Compare commits

...

23 Commits
8.0.0 ... main

Author SHA1 Message Date
Paul Schaub 1d80ff1d8d
Update changelog 2024-03-30 19:02:58 +01:00
Paul Schaub 9356447226
Remove label() option from armor() operation 2024-03-30 19:00:09 +01:00
Paul Schaub a13f1e2a0d
Mark ProxyOutputStream as deprecated 2024-03-27 21:57:04 +01:00
Paul Schaub e39cc7f0ac
Remove deprecated junit5-system-exit
Replaced with custom test DSL that avoids System.exit
2024-03-27 21:50:01 +01:00
Paul Schaub cbbdd09472
SOP-Java 10.0.1-SNAPSHOT 2024-03-21 14:06:42 +01:00
Paul Schaub da6cba1d55
SOP-Java 10.0.0 2024-03-21 14:01:48 +01:00
Paul Schaub 4b2875d572
Update changelog 2024-03-21 13:54:56 +01:00
Paul Schaub 30f7ca90cd
Bump logback-core and logback-classic from 1.2.11 to 1.2.13 2024-03-21 13:54:56 +01:00
Paul Schaub bfa97aede8
Add description of external-sop module 2024-03-21 13:54:56 +01:00
Paul Schaub bdbc9593c8
Update spec revision and badge link 2024-03-21 13:54:56 +01:00
Paul Schaub 3643aff082
Bump version to 10.0.0 2024-03-21 13:54:55 +01:00
Paul Schaub ed9b2f5fef
Move signature verification operations to sopv interface subset 2024-03-21 13:54:55 +01:00
Paul Schaub cd208c8942
Add test ckecking that BadData is thrown if KEYS is passed for CERTS 2024-03-21 13:54:55 +01:00
Paul Schaub 7325cad696
Replace assumeTrue(false) with explicit TestAbortedException 2024-03-21 13:54:55 +01:00
Paul Schaub 7a825c7607
Fix javadoc reference 2024-03-21 13:54:55 +01:00
Paul Schaub 03f8950b16
Rename woodpecker files 2024-03-17 18:05:35 +01:00
Paul Schaub d5d7d67d6f
Fix reuse compliance 2024-03-17 17:58:04 +01:00
Paul Schaub e2a568e73e
Update issue templates 2024-02-26 11:03:16 +01:00
Paul Schaub 7092baee4f
SOP-Java 8.0.2-SNAPSHOT 2023-11-22 18:21:41 +01:00
Paul Schaub 592aecd646
SOP-Java 8.0.1 2023-11-22 18:19:13 +01:00
Paul Schaub e5e64003f3
decrypt: Do not throw NoSignature exception when verifications is empty 2023-11-22 17:23:06 +01:00
Paul Schaub 51d9c29837
decrypt --verify-with: Do not expect exit 3 when verifications is empty 2023-11-22 17:23:06 +01:00
Paul Schaub ae83ddcff6
SOP-Java 8.0.1-SNAPSHOT 2023-11-15 19:01:50 +01:00
39 changed files with 852 additions and 289 deletions

29
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,29 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**Version**
<!-- What versions of the following libraries are you using? -->
- `sop-java`:
- `pgpainless-core`:
- `bouncycastle`:
**To Reproduce**
<!-- Steps to reproduce the behavior: -->
```
Example Code Block
```
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Additional context**
<!-- Add any other context about the problem here. -->

View File

@ -22,3 +22,8 @@ License: Apache-2.0
Files: external-sop/src/main/resources/sop/testsuite/external/*
Copyright: 2023 the original author or authors
License: Apache-2.0
# Github Issue Templates
Files: .github/ISSUE_TEMPLATE/*
Copyright: 2024 the original author or authors
License: Apache-2.0

View File

@ -6,6 +6,18 @@ SPDX-License-Identifier: Apache-2.0
# Changelog
## 10.0.1-SNAPSHOT
- Remove `label()` option from `Armor` operation
## 10.0.0
- Update implementation to [SOP Specification revision 10](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-10.html).
- Throw `BadData` when passing KEYS where CERTS are expected
- Introduce `sopv` interface subset with revision `1.0`
- Add `sop version --sopv`
## 8.0.1
- `decrypt`: Do not throw `NoSignature` exception (exit code 3) if `--verify-with` is provided, but `VERIFICATIONS` is empty.
## 8.0.0
- Rewrote `sop-java` in Kotlin
- Rewrote `sop-java-picocli` in Kotlin

View File

@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0
# SOP for Java
[![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java)
[![Spec Revision: 8](https://img.shields.io/badge/Spec%20Revision-8-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/08/)
[![Spec Revision: 10](https://img.shields.io/badge/Spec%20Revision-10-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/10/)
[![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main)
[![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java)
@ -25,6 +25,8 @@ The repository contains the following modules:
* [sop-java](/sop-java) defines a set of Java interfaces describing the Stateless OpenPGP Protocol.
* [sop-java-picocli](/sop-java-picocli) contains a wrapper application that transforms the `sop-java` API into a command line application
compatible with the SOP-CLI specification.
* [external-sop](/external-sop) contains an API implementation that can be used to forward API calls to a SOP executable,
allowing to delegate the implementation logic to an arbitrary SOP CLI implementation.
## Known Implementations
(Please expand!)

View File

@ -16,7 +16,7 @@ import sop.external.operation.*
import sop.operation.*
/**
* Implementation of the {@link SOP} API using an external SOP binary.
* Implementation of the [SOP] API using an external SOP binary.
*
* Instantiate an [ExternalSOP] object for the given binary and the given [TempDirProvider] using
* empty environment variables.

View File

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.external
import java.nio.file.Files
import java.util.*
import sop.SOPV
import sop.external.ExternalSOP.TempDirProvider
import sop.external.operation.DetachedVerifyExternal
import sop.external.operation.InlineVerifyExternal
import sop.external.operation.VersionExternal
import sop.operation.DetachedVerify
import sop.operation.InlineVerify
import sop.operation.Version
/**
* Implementation of the [SOPV] API subset using an external sopv/sop binary.
*
* Instantiate an [ExternalSOPV] object for the given binary and the given [TempDirProvider] using
* empty environment variables.
*
* @param binaryName name / path of the sopv binary
* @param tempDirProvider custom tempDirProvider
*/
class ExternalSOPV(
private val binaryName: String,
private val properties: Properties = Properties(),
private val tempDirProvider: TempDirProvider = defaultTempDirProvider()
) : SOPV {
override fun version(): Version = VersionExternal(binaryName, properties)
override fun detachedVerify(): DetachedVerify = DetachedVerifyExternal(binaryName, properties)
override fun inlineVerify(): InlineVerify =
InlineVerifyExternal(binaryName, properties, tempDirProvider)
companion object {
/**
* Default implementation of the [TempDirProvider] which stores temporary files in the
* systems temp dir ([Files.createTempDirectory]).
*
* @return default implementation
*/
@JvmStatic
fun defaultTempDirProvider(): TempDirProvider {
return TempDirProvider { Files.createTempDirectory("ext-sopv").toFile() }
}
}
}

View File

@ -7,7 +7,6 @@ package sop.external.operation
import java.io.InputStream
import java.util.Properties
import sop.Ready
import sop.enums.ArmorLabel
import sop.exception.SOPGPException
import sop.external.ExternalSOP
import sop.operation.Armor
@ -18,8 +17,6 @@ class ArmorExternal(binary: String, environment: Properties) : Armor {
private val commandList: MutableList<String> = mutableListOf(binary, "armor")
private val envList: List<String> = ExternalSOP.propertiesToEnv(environment)
override fun label(label: ArmorLabel): Armor = apply { commandList.add("--label=$label") }
@Throws(SOPGPException.BadData::class)
override fun data(data: InputStream): Ready =
ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data)

View File

@ -68,6 +68,10 @@ class VersionExternal(binary: String, environment: Properties) : Version {
return null
}
override fun getSopVVersion(): String {
return executeForLines(commandList.plus("--sopv"))
}
override fun getSopSpecVersion(): String {
return executeForLines(commandList.plus("--sop-spec"))
}

View File

@ -12,15 +12,12 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Testing Exit Codes in JUnit
// https://todd.ginsberg.com/post/testing-system-exit/
testImplementation "com.ginsberg:junit5-system-exit:$junitSysExitVersion"
// Mocking Components
testImplementation "org.mockito:mockito-core:$mockitoVersion"
// SOP
implementation(project(":sop-java"))
testImplementation(testFixtures(project(":sop-java")))
// CLI
implementation "info.picocli:picocli:$picocliVersion"

View File

@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.cli.picocli
import java.util.*
import kotlin.system.exitProcess
import picocli.AutoComplete
import picocli.CommandLine
import sop.SOPV
import sop.cli.picocli.commands.*
import sop.exception.SOPGPException
@CommandLine.Command(
name = "sopv",
resourceBundle = "msg_sop",
exitCodeOnInvalidInput = SOPGPException.UnsupportedSubcommand.EXIT_CODE,
subcommands =
[
// Meta subcommands
VersionCmd::class,
// signature verification subcommands
VerifyCmd::class,
InlineVerifyCmd::class,
// misc
CommandLine.HelpCommand::class,
AutoComplete.GenerateCompletion::class])
class SopVCLI {
companion object {
@JvmStatic private var sopvInstance: SOPV? = null
@JvmStatic
fun getSopV(): SOPV =
checkNotNull(sopvInstance) { cliMsg.getString("sop.error.runtime.no_backend_set") }
@JvmStatic
fun setSopVInstance(sopv: SOPV?) {
sopvInstance = sopv
}
@JvmField var cliMsg: ResourceBundle = ResourceBundle.getBundle("msg_sop")
@JvmField var EXECUTABLE_NAME = "sopv"
@JvmField
@CommandLine.Option(names = ["--stacktrace"], scope = CommandLine.ScopeType.INHERIT)
var stacktrace = false
@JvmStatic
fun main(vararg args: String) {
val exitCode = execute(*args)
if (exitCode != 0) {
exitProcess(exitCode)
}
}
@JvmStatic
fun execute(vararg args: String): Int {
// Set locale
CommandLine(InitLocale()).parseArgs(*args)
// Re-set bundle with updated locale
cliMsg = ResourceBundle.getBundle("msg_sop")
return CommandLine(SopVCLI::class.java)
.apply {
// explicitly set help command resource bundle
subcommands["help"]?.setResourceBundle(ResourceBundle.getBundle("msg_help"))
// Hide generate-completion command
subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true)
// overwrite executable name
commandName = EXECUTABLE_NAME
// setup exception handling
executionExceptionHandler = SOPExecutionExceptionHandler()
exitCodeExceptionMapper = SOPExceptionExitCodeMapper()
isCaseInsensitiveEnumValuesAllowed = true
}
.execute(*args)
}
}
/**
* Control the locale.
*
* @see <a href="https://picocli.info/#_controlling_the_locale">Picocli Readme</a>
*/
@CommandLine.Command
class InitLocale {
@CommandLine.Option(names = ["-l", "--locale"], descriptionKey = "sop.locale")
fun setLocale(locale: String) = Locale.setDefault(Locale(locale))
@CommandLine.Unmatched
var remainder: MutableList<String> =
mutableListOf() // ignore any other parameters and options in the first parsing phase
}
}

View File

@ -85,13 +85,10 @@ class DecryptCmd : AbstractSopCmd() {
@Throws(IOException::class)
private fun writeVerifyOut(result: DecryptionResult) {
verifyOut?.let {
if (result.verifications.isEmpty()) {
val errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found")
throw NoSignature(errorMsg)
}
getOutput(verifyOut).use { out ->
PrintWriter(out).use { pw -> result.verifications.forEach { pw.println(it) } }
getOutput(it).use { out ->
PrintWriter(out).use { pw ->
result.verifications.forEach { verification -> pw.println(verification) }
}
}
}
}

View File

@ -22,6 +22,7 @@ class VersionCmd : AbstractSopCmd() {
@Option(names = ["--extended"]) var extended: Boolean = false
@Option(names = ["--backend"]) var backend: Boolean = false
@Option(names = ["--sop-spec"]) var sopSpec: Boolean = false
@Option(names = ["--sopv"]) var sopv: Boolean = false
}
override fun run() {
@ -47,5 +48,10 @@ class VersionCmd : AbstractSopCmd() {
println(version.getSopSpecVersion())
return
}
if (exclusive!!.sopv) {
println(version.getSopVVersion())
return
}
}
}

View File

@ -2,7 +2,6 @@
#
# SPDX-License-Identifier: Apache-2.0
usage.header=Add ASCII Armor to standard input
label=Label to be used in the header and tail of the armoring
stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0

View File

@ -2,7 +2,6 @@
#
# SPDX-License-Identifier: Apache-2.0
usage.header=Schütze Standard-Eingabe mit ASCII Armor
label=Label für Kopf- und Fußzeile der ASCII Armor
stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0

View File

@ -2,7 +2,9 @@
#
# SPDX-License-Identifier: Apache-2.0
sop.name=sop
sopv.name=sopv
usage.header=Stateless OpenPGP Protocol
sopv.usage.header=Stateless OpenPGP Protocol - Signature Verification Interface Subset
locale=Locale for description texts
# Generic

View File

@ -2,7 +2,9 @@
#
# SPDX-License-Identifier: Apache-2.0
sop.name=sop
sopv.name=sopv
usage.header=Stateless OpenPGP Protocol
sopv.usage.header=Stateless OpenPGP Protocol - Signature Verification Interface Subset
locale=Gebietsschema für Beschreibungstexte
# Generic

View File

@ -6,12 +6,13 @@ package sop.cli.picocli;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedSubcommand;
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.exception.SOPGPException;
@ -34,20 +35,18 @@ import sop.operation.Version;
public class SOPTest {
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedSubcommand.EXIT_CODE)
public void assertExitOnInvalidSubcommand() {
SOP sop = mock(SOP.class);
SopCLI.setSopInstance(sop);
SopCLI.main(new String[] {"invalid"});
assertUnsupportedSubcommand(() -> SopCLI.execute("invalid"));
}
@Test
@ExpectSystemExitWithStatus(1)
public void assertThrowsIfNoSOPBackendSet() {
SopCLI.setSopInstance(null);
// At this point, no SOP backend is set, so an InvalidStateException triggers exit(1)
SopCLI.main(new String[] {"armor"});
// At this point, no SOP backend is set, so an InvalidStateException triggers error code 1
assertGenericError(() -> SopCLI.execute("armor"));
}
@Test

View File

@ -4,8 +4,6 @@
package sop.cli.picocli.commands;
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;
@ -24,6 +22,8 @@ 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 static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
public class ArmorCmdTest {
@ -42,24 +42,22 @@ public class ArmorCmdTest {
@Test
public void assertDataIsAlwaysCalled() throws SOPGPException.BadData, IOException {
SopCLI.main(new String[] {"armor"});
assertSuccess(() -> SopCLI.execute("armor"));
verify(armor, times(1)).data((InputStream) any());
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void ifBadDataExit41() throws SOPGPException.BadData, IOException {
when(armor.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"armor"});
assertBadData(() -> SopCLI.execute("armor"));
}
@Test
@FailOnSystemExit
public void ifNoErrorsNoExit() {
when(sop.armor()).thenReturn(armor);
SopCLI.main(new String[] {"armor"});
assertSuccess(() -> SopCLI.execute("armor"));
}
private static Ready nopReady() {

View File

@ -9,12 +9,13 @@ 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 static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
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;
@ -48,14 +49,13 @@ public class DearmorCmdTest {
@Test
public void assertDataIsCalled() throws IOException, SOPGPException.BadData {
SopCLI.main(new String[] {"dearmor"});
assertSuccess(() -> SopCLI.execute("dearmor"));
verify(dearmor, times(1)).data((InputStream) any());
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
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"});
assertBadData(() -> SopCLI.execute("dearmor"));
}
}

View File

@ -4,7 +4,6 @@
package sop.cli.picocli.commands;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher;
@ -21,6 +20,7 @@ import sop.operation.Decrypt;
import sop.util.HexUtil;
import sop.util.UTCUtil;
import javax.annotation.Nonnull;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -41,6 +41,18 @@ 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 static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertCannotDecrypt;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertIncompleteVerification;
import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyIsProtected;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput;
import static sop.testsuite.assertions.SopExecutionAssertions.assertOutputExists;
import static sop.testsuite.assertions.SopExecutionAssertions.assertPasswordNotHumanReadable;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedAsymmetricAlgo;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption;
public class DecryptCmdTest {
@ -73,47 +85,47 @@ public class DecryptCmdTest {
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE)
public void missingArgumentsExceptionCausesExit19() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt, IOException {
when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.MissingArg("Missing arguments."));
SopCLI.main(new String[] {"decrypt"});
assertMissingArg(() -> SopCLI.execute("decrypt"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void badDataExceptionCausesExit41() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt, IOException {
when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"decrypt"});
assertBadData(() -> SopCLI.execute("decrypt"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.PasswordNotHumanReadable.EXIT_CODE)
public void assertNotHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable,
SOPGPException.UnsupportedOption, IOException {
File passwordFile = TestFileUtil.writeTempStringFile("pretendThisIsNotReadable");
when(decrypt.withPassword(any())).thenThrow(new SOPGPException.PasswordNotHumanReadable());
SopCLI.main(new String[] {"decrypt", "--with-password", passwordFile.getAbsolutePath()});
assertPasswordNotHumanReadable(() ->
SopCLI.execute("decrypt", "--with-password", passwordFile.getAbsolutePath())
);
}
@Test
public void assertWithPasswordPassesPasswordDown() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
File passwordFile = TestFileUtil.writeTempStringFile("orange");
SopCLI.main(new String[] {"decrypt", "--with-password", passwordFile.getAbsolutePath()});
assertSuccess(() -> SopCLI.execute("decrypt", "--with-password", passwordFile.getAbsolutePath()));
verify(decrypt, times(1)).withPassword("orange");
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void assertUnsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
File passwordFile = TestFileUtil.writeTempStringFile("swordfish");
when(decrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Decrypting with password not supported."));
SopCLI.main(new String[] {"decrypt", "--with-password", passwordFile.getAbsolutePath()});
assertUnsupportedOption(() ->
SopCLI.execute("decrypt", "--with-password", passwordFile.getAbsolutePath())
);
}
@Test
public void assertDefaultTimeRangesAreUsedIfNotOverwritten() throws SOPGPException.UnsupportedOption {
Date now = new Date();
SopCLI.main(new String[] {"decrypt"});
assertSuccess(() -> SopCLI.execute("decrypt"));
verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME);
verify(decrypt, times(1)).verifyNotAfter(
ArgumentMatchers.argThat(argument -> {
@ -124,7 +136,8 @@ public class DecryptCmdTest {
@Test
public void assertVerifyNotAfterAndBeforeDashResultsInMaxTimeRange() throws SOPGPException.UnsupportedOption {
SopCLI.main(new String[] {"decrypt", "--verify-not-before", "-", "--verify-not-after", "-"});
assertSuccess(() ->
SopCLI.execute("decrypt", "--verify-not-before", "-", "--verify-not-after", "-"));
verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME);
verify(decrypt, times(1)).verifyNotAfter(AbstractSopCmd.END_OF_TIME);
}
@ -137,54 +150,57 @@ public class DecryptCmdTest {
return Math.abs(now.getTime() - argument.getTime()) <= 1000;
};
SopCLI.main(new String[] {"decrypt", "--verify-not-before", "now", "--verify-not-after", "now"});
assertSuccess(() ->
SopCLI.execute("decrypt", "--verify-not-before", "now", "--verify-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", "--verify-not-before", "invalid"});
assertGenericError(() ->
SopCLI.execute("decrypt", "--verify-not-before", "invalid"));
}
@Test
@ExpectSystemExitWithStatus(1)
public void assertMalformedDateInNotAfterCausesExit1() {
// ParserException causes exit(1)
SopCLI.main(new String[] {"decrypt", "--verify-not-after", "invalid"});
assertGenericError(() ->
SopCLI.execute("decrypt", "--verify-not-after", "invalid"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
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", "--verify-not-after", "now"});
when(decrypt.verifyNotAfter(any())).thenThrow(
new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported."));
assertUnsupportedOption(() ->
SopCLI.execute("decrypt", "--verify-not-after", "now"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
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", "--verify-not-before", "now"});
when(decrypt.verifyNotBefore(any())).thenThrow(
new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported."));
assertUnsupportedOption(() ->
SopCLI.execute("decrypt", "--verify-not-before", "now"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.OutputExists.EXIT_CODE)
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()});
assertOutputExists(() ->
SopCLI.execute("decrypt", "--session-key-out", tempFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
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()});
assertUnsupportedOption(() ->
SopCLI.execute("decrypt", "--session-key-out", tempFile.getAbsolutePath()));
}
@Test
@ -209,8 +225,10 @@ public class DecryptCmdTest {
File verificationsFile = new File(tempDir.toFile(), "verifications");
File keyFile = new File(tempDir.toFile(), "key.asc");
keyFile.createNewFile();
SopCLI.main(new String[] {"decrypt", "--session-key-out", sessionKeyFile.getAbsolutePath(),
"--verifications-out", verificationsFile.getAbsolutePath(), "--verify-with", keyFile.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("decrypt", "--session-key-out", sessionKeyFile.getAbsolutePath(),
"--verifications-out", verificationsFile.getAbsolutePath(), "--verify-with",
keyFile.getAbsolutePath()));
ByteArrayOutputStream bytesInFile = new ByteArrayOutputStream();
try (FileInputStream fileIn = new FileInputStream(sessionKeyFile)) {
@ -240,45 +258,49 @@ public class DecryptCmdTest {
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.CannotDecrypt.EXIT_CODE)
public void assertUnableToDecryptExceptionResultsInExit29() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException {
when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.CannotDecrypt());
SopCLI.main(new String[] {"decrypt"});
assertCannotDecrypt(() ->
SopCLI.execute("decrypt"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.NoSignature.EXIT_CODE)
public void assertNoSignatureExceptionCausesExit3() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException {
public void assertNoVerificationsIsOkay() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException {
File tempFile = File.createTempFile("verify-with-", ".tmp");
File verifyOut = new File(tempFile.getParent(), "verifications.out");
verifyOut.deleteOnExit();
when(decrypt.ciphertext((InputStream) any())).thenReturn(new ReadyWithResult<DecryptionResult>() {
@Override
public DecryptionResult writeTo(OutputStream outputStream) throws SOPGPException.NoSignature {
throw new SOPGPException.NoSignature();
public DecryptionResult writeTo(@Nonnull OutputStream outputStream) throws SOPGPException.NoSignature {
return new DecryptionResult(null, Collections.emptyList());
}
});
SopCLI.main(new String[] {"decrypt"});
assertSuccess(() ->
SopCLI.execute("decrypt", "--verify-with", tempFile.getAbsolutePath(), "--verifications-out",
verifyOut.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
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()});
assertBadData(() ->
SopCLI.execute("decrypt", "--verify-with", tempFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void unexistentCertFileCausesExit61() {
SopCLI.main(new String[] {"decrypt", "--verify-with", "invalid"});
assertMissingInput(() ->
SopCLI.execute("decrypt", "--verify-with", "invalid"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.OutputExists.EXIT_CODE)
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", "--verifications-out", existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()});
assertOutputExists(() -> SopCLI.execute("decrypt", "--verifications-out",
existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()));
}
@Test
@ -302,7 +324,9 @@ public class DecryptCmdTest {
}
});
SopCLI.main(new String[] {"decrypt", "--verifications-out", verifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("decrypt", "--verifications-out", verifyOut.getAbsolutePath(),
"--verify-with", certFile.getAbsolutePath()));
try (BufferedReader reader = new BufferedReader(new FileReader(verifyOut))) {
String line = reader.readLine();
assertEquals("2021-07-11T20:58:23Z 1B66A707819A920925BC6777C3E0AFC0B2DFF862 C8CD564EBF8D7BBA90611D8D071773658BF6BF86", line);
@ -317,66 +341,64 @@ public class DecryptCmdTest {
File sessionKeyFile1 = TestFileUtil.writeTempStringFile(key1.toString());
File sessionKeyFile2 = TestFileUtil.writeTempStringFile(key2.toString());
SopCLI.main(new String[] {"decrypt",
"--with-session-key", sessionKeyFile1.getAbsolutePath(),
"--with-session-key", sessionKeyFile2.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("decrypt",
"--with-session-key", sessionKeyFile1.getAbsolutePath(),
"--with-session-key", sessionKeyFile2.getAbsolutePath()));
verify(decrypt).withSessionKey(key1);
verify(decrypt).withSessionKey(key2);
}
@Test
@ExpectSystemExitWithStatus(1)
public void assertMalformedSessionKeysResultInExit1() throws IOException {
File sessionKeyFile = TestFileUtil.writeTempStringFile("C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137");
SopCLI.main(new String[] {"decrypt",
"--with-session-key", sessionKeyFile.getAbsolutePath()});
assertGenericError(() ->
SopCLI.execute("decrypt",
"--with-session-key", sessionKeyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
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()});
assertBadData(() -> SopCLI.execute("decrypt", tempKeyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void assertKeyFileNotFoundCausesExit61() {
SopCLI.main(new String[] {"decrypt", "nonexistent-key"});
assertMissingInput(() -> SopCLI.execute("decrypt", "nonexistent-key"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.KeyIsProtected.EXIT_CODE)
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()});
assertKeyIsProtected(() -> SopCLI.execute("decrypt", tempKeyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE)
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()});
assertUnsupportedAsymmetricAlgo(() ->
SopCLI.execute("decrypt", tempKeyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void assertMissingPassphraseFileCausesExit61() {
SopCLI.main(new String[] {"decrypt", "--with-password", "missing"});
assertMissingInput(() ->
SopCLI.execute("decrypt", "--with-password", "missing"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void assertMissingSessionKeyFileCausesExit61() {
SopCLI.main(new String[] {"decrypt", "--with-session-key", "missing"});
assertMissingInput(() ->
SopCLI.execute("decrypt", "--with-session-key", "missing"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.IncompleteVerification.EXIT_CODE)
public void verifyOutWithoutVerifyWithCausesExit23() {
SopCLI.main(new String[] {"decrypt", "--verifications-out", "out.file"});
assertIncompleteVerification(() ->
SopCLI.execute("decrypt", "--verifications-out", "out.file"));
}
}

View File

@ -4,7 +4,6 @@
package sop.cli.picocli.commands;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -28,6 +27,17 @@ 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 static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertCertCannotEncrypt;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyCannotSign;
import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyIsProtected;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput;
import static sop.testsuite.assertions.SopExecutionAssertions.assertPasswordNotHumanReadable;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedAsymmetricAlgo;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption;
public class EncryptCmdTest {
@ -50,48 +60,50 @@ public class EncryptCmdTest {
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE)
public void missingBothPasswordAndCertFileCauseExit19() {
SopCLI.main(new String[] {"encrypt", "--no-armor"});
public void missingBothPasswordAndCertFileCausesMissingArg() {
assertMissingArg(() ->
SopCLI.execute("encrypt", "--no-armor"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void as_unsupportedEncryptAsCausesExit37() throws SOPGPException.UnsupportedOption {
public void as_unsupportedEncryptAsCausesUnsupportedOption() throws SOPGPException.UnsupportedOption {
when(encrypt.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting encryption mode not supported."));
SopCLI.main(new String[] {"encrypt", "--as", "Binary"});
assertUnsupportedOption(() ->
SopCLI.execute("encrypt", "--as", "Binary"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void as_invalidModeOptionCausesExit37() {
SopCLI.main(new String[] {"encrypt", "--as", "invalid"});
public void as_invalidModeOptionCausesUnsupportedOption() {
assertUnsupportedOption(() ->
SopCLI.execute("encrypt", "--as", "invalid"));
}
@Test
public void as_modeIsPassedDown() throws SOPGPException.UnsupportedOption, IOException {
File passwordFile = TestFileUtil.writeTempStringFile("0rbit");
for (EncryptAs mode : EncryptAs.values()) {
SopCLI.main(new String[] {"encrypt", "--as", mode.name(), "--with-password", passwordFile.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("encrypt", "--as", mode.name(),
"--with-password", passwordFile.getAbsolutePath()));
verify(encrypt, times(1)).mode(mode);
}
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.PasswordNotHumanReadable.EXIT_CODE)
public void withPassword_notHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
public void withPassword_notHumanReadablePasswordCausesPWNotHumanReadable() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
when(encrypt.withPassword("pretendThisIsNotReadable")).thenThrow(new SOPGPException.PasswordNotHumanReadable());
File passwordFile = TestFileUtil.writeTempStringFile("pretendThisIsNotReadable");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()});
assertPasswordNotHumanReadable(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void withPassword_unsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
public void withPassword_unsupportedWithPasswordCausesUnsupportedOption() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
when(encrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Encrypting with password not supported."));
File passwordFile = TestFileUtil.writeTempStringFile("orange");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()});
assertUnsupportedOption(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath()));
}
@Test
@ -99,99 +111,107 @@ public class EncryptCmdTest {
File keyFile1 = File.createTempFile("sign-with-1-", ".asc");
File keyFile2 = File.createTempFile("sign-with-2-", ".asc");
File passwordFile = TestFileUtil.writeTempStringFile("password");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile1.getAbsolutePath(), "--sign-with", keyFile2.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(),
"--sign-with", keyFile1.getAbsolutePath(),
"--sign-with", keyFile2.getAbsolutePath()));
verify(encrypt, times(2)).signWith((InputStream) any());
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void signWith_nonExistentKeyFileCausesExit61() {
SopCLI.main(new String[] {"encrypt", "--with-password", "admin", "--sign-with", "nonExistent.asc"});
public void signWith_nonExistentKeyFileCausesMissingInput() {
assertMissingInput(() ->
SopCLI.execute("encrypt", "--with-password", "admin", "--sign-with", "nonExistent.asc"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.KeyIsProtected.EXIT_CODE)
public void signWith_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
public void signWith_keyIsProtectedCausesKeyIsProtected() 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");
File passwordFile = TestFileUtil.writeTempStringFile("starship");
SopCLI.main(new String[] {"encrypt", "--sign-with", keyFile.getAbsolutePath(), "--with-password", passwordFile.getAbsolutePath()});
assertKeyIsProtected(() ->
SopCLI.execute("encrypt", "--sign-with", keyFile.getAbsolutePath(),
"--with-password", passwordFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE)
public void signWith_unsupportedAsymmetricAlgoCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
public void signWith_unsupportedAsymmetricAlgoCausesUnsupportedAsymAlgo() 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");
File passwordFile = TestFileUtil.writeTempStringFile("123456");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile.getAbsolutePath()});
assertUnsupportedAsymmetricAlgo(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(),
"--sign-with", keyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.KeyCannotSign.EXIT_CODE)
public void signWith_certCannotSignCausesExit79() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData {
public void signWith_certCannotSignCausesKeyCannotSign() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData {
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyCannotSign());
File keyFile = File.createTempFile("sign-with", ".asc");
File passwordFile = TestFileUtil.writeTempStringFile("dragon");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile.getAbsolutePath()});
assertKeyCannotSign(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(),
"--sign-with", keyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void signWith_badDataCausesExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
public void signWith_badDataCausesBadData() 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");
File passwordFile = TestFileUtil.writeTempStringFile("orange");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile.getAbsolutePath()});
assertBadData(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(),
"--sign-with", keyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void cert_nonExistentCertFileCausesExit61() {
SopCLI.main(new String[] {"encrypt", "invalid.asc"});
public void cert_nonExistentCertFileCausesMissingInput() {
assertMissingInput(() ->
SopCLI.execute("encrypt", "invalid.asc"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE)
public void cert_unsupportedAsymmetricAlgorithmCausesExit13() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
public void cert_unsupportedAsymmetricAlgorithmCausesUnsupportedAsymAlg() 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()});
assertUnsupportedAsymmetricAlgo(() ->
SopCLI.execute("encrypt", certFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.CertCannotEncrypt.EXIT_CODE)
public void cert_certCannotEncryptCausesExit17() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
public void cert_certCannotEncryptCausesCertCannotEncrypt() 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()});
assertCertCannotEncrypt(() ->
SopCLI.execute("encrypt", certFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void cert_badDataCausesExit41() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
public void cert_badDataCausesBadData() 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()});
assertBadData(() ->
SopCLI.execute("encrypt", certFile.getAbsolutePath()));
}
@Test
public void noArmor_notCalledByDefault() throws IOException {
File passwordFile = TestFileUtil.writeTempStringFile("clownfish");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath()));
verify(encrypt, never()).noArmor();
}
@Test
public void noArmor_callGetsPassedDown() throws IOException {
File passwordFile = TestFileUtil.writeTempStringFile("monkey");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--no-armor"});
assertSuccess(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(), "--no-armor"));
verify(encrypt, times(1)).noArmor();
}
@Test
@ExpectSystemExitWithStatus(1)
public void writeTo_ioExceptionCausesExit1() throws IOException {
public void writeTo_ioExceptionCausesGenericError() throws IOException {
when(encrypt.plaintext((InputStream) any())).thenReturn(new ReadyWithResult<EncryptionResult>() {
@Override
public EncryptionResult writeTo(@NotNull OutputStream outputStream) throws IOException, SOPGPException {
@ -199,6 +219,7 @@ public class EncryptCmdTest {
}
});
File passwordFile = TestFileUtil.writeTempStringFile("wildcat");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()});
assertGenericError(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath()));
}
}

View File

@ -10,12 +10,14 @@ 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 static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
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;
@ -45,32 +47,34 @@ public class ExtractCertCmdTest {
@Test
public void noArmor_notCalledByDefault() {
SopCLI.main(new String[] {"extract-cert"});
assertSuccess(() ->
SopCLI.execute("extract-cert"));
verify(extractCert, never()).noArmor();
}
@Test
public void noArmor_passedDown() {
SopCLI.main(new String[] {"extract-cert", "--no-armor"});
assertSuccess(() ->
SopCLI.execute("extract-cert", "--no-armor"));
verify(extractCert, times(1)).noArmor();
}
@Test
@ExpectSystemExitWithStatus(1)
public void key_ioExceptionCausesExit1() throws IOException, SOPGPException.BadData {
public void key_ioExceptionCausesGenericError() 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"});
assertGenericError(() ->
SopCLI.execute("extract-cert"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void key_badDataCausesExit41() throws IOException, SOPGPException.BadData {
public void key_badDataCausesBadData() throws IOException, SOPGPException.BadData {
when(extractCert.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"extract-cert"});
assertBadData(() ->
SopCLI.execute("extract-cert"));
}
}

View File

@ -10,11 +10,14 @@ 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 static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedAsymmetricAlgo;
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;
@ -47,19 +50,22 @@ public class GenerateKeyCmdTest {
@Test
public void noArmor_notCalledByDefault() {
SopCLI.main(new String[] {"generate-key", "Alice"});
assertSuccess(() ->
SopCLI.execute("generate-key", "Alice"));
verify(generateKey, never()).noArmor();
}
@Test
public void noArmor_passedDown() {
SopCLI.main(new String[] {"generate-key", "--no-armor", "Alice"});
assertSuccess(() ->
SopCLI.execute("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>"});
assertSuccess(() ->
SopCLI.execute("generate-key", "Alice <alice@pgpainless.org>", "Bob <bob@pgpainless.org>"));
InOrder inOrder = Mockito.inOrder(generateKey);
inOrder.verify(generateKey).userId("Alice <alice@pgpainless.org>");
@ -69,30 +75,32 @@ public class GenerateKeyCmdTest {
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE)
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"});
assertMissingArg(() ->
SopCLI.execute("generate-key"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE)
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"});
assertUnsupportedAsymmetricAlgo(() ->
SopCLI.execute("generate-key", "Alice"));
}
@Test
@ExpectSystemExitWithStatus(1)
public void ioExceptionCausesExit1() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
public void ioExceptionCausesGenericError() 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"});
assertGenericError(() ->
SopCLI.execute("generate-key", "Alice"));
}
}

View File

@ -4,7 +4,6 @@
package sop.cli.picocli.commands;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import sop.ReadyWithResult;
@ -26,6 +25,8 @@ 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 static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
public class InlineDetachCmdTest {
@ -41,9 +42,9 @@ public class InlineDetachCmdTest {
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE)
public void testMissingSignaturesOutResultsInExit19() {
SopCLI.main(new String[] {"inline-detach"});
public void testMissingSignaturesOutResultsInMissingArg() {
assertMissingArg(() ->
SopCLI.execute("inline-detach"));
}
@Test
@ -67,7 +68,8 @@ public class InlineDetachCmdTest {
}
});
SopCLI.main(new String[] {"inline-detach", "--signatures-out", tempFile.getAbsolutePath(), "--no-armor"});
assertSuccess(() ->
SopCLI.execute("inline-detach", "--signatures-out", tempFile.getAbsolutePath(), "--no-armor"));
verify(inlineDetach, times(1)).noArmor();
verify(inlineDetach, times(1)).message((InputStream) any());
}

View File

@ -10,13 +10,20 @@ 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 static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertExpectedText;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyIsProtected;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption;
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;
@ -54,70 +61,77 @@ public class SignCmdTest {
@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()});
assertSuccess(() ->
SopCLI.execute("sign", "--as", "Binary", keyFile.getAbsolutePath()));
assertSuccess(() ->
SopCLI.execute("sign", "--as", "binary", keyFile.getAbsolutePath()));
assertSuccess(() ->
SopCLI.execute("sign", "--as", "BINARY", keyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void as_invalidOptionCausesExit37() {
SopCLI.main(new String[] {"sign", "--as", "Invalid", keyFile.getAbsolutePath()});
assertUnsupportedOption(() ->
SopCLI.execute("sign", "--as", "Invalid", keyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void as_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
when(detachedSign.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting signing mode not supported."));
SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()});
assertUnsupportedOption(() ->
SopCLI.execute("sign", "--as", "binary", keyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void key_nonExistentKeyFileCausesExit61() {
SopCLI.main(new String[] {"sign", "invalid.asc"});
assertMissingInput(() ->
SopCLI.execute("sign", "invalid.asc"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.KeyIsProtected.EXIT_CODE)
public void key_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData {
when(detachedSign.key((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected());
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
assertKeyIsProtected(() ->
SopCLI.execute("sign", keyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void key_badDataCausesExit41() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData {
when(detachedSign.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
assertBadData(() ->
SopCLI.execute("sign", keyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE)
public void key_missingKeyFileCausesExit19() {
SopCLI.main(new String[] {"sign"});
assertMissingArg(() ->
SopCLI.execute("sign"));
}
@Test
public void noArmor_notCalledByDefault() {
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("sign", keyFile.getAbsolutePath()));
verify(detachedSign, never()).noArmor();
}
@Test
public void noArmor_passedDown() {
SopCLI.main(new String[] {"sign", "--no-armor", keyFile.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("sign", "--no-armor", keyFile.getAbsolutePath()));
verify(detachedSign, times(1)).noArmor();
}
@Test
public void withKeyPassword_passedDown() {
SopCLI.main(new String[] {"sign", "--with-key-password", passFile.getAbsolutePath(), keyFile.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("sign",
"--with-key-password", passFile.getAbsolutePath(),
keyFile.getAbsolutePath()));
verify(detachedSign, times(1)).withKeyPassword("sw0rdf1sh");
}
@Test
@ExpectSystemExitWithStatus(1)
public void data_ioExceptionCausesExit1() throws IOException, SOPGPException.ExpectedText {
when(detachedSign.data((InputStream) any())).thenReturn(new ReadyWithResult<SigningResult>() {
@Override
@ -125,13 +139,14 @@ public class SignCmdTest {
throw new IOException();
}
});
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
assertGenericError(() ->
SopCLI.execute("sign", keyFile.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.ExpectedText.EXIT_CODE)
public void data_expectedTextExceptionCausesExit53() throws IOException, SOPGPException.ExpectedText {
when(detachedSign.data((InputStream) any())).thenThrow(new SOPGPException.ExpectedText());
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
assertExpectedText(() ->
SopCLI.execute("sign", keyFile.getAbsolutePath()));
}
}

View File

@ -10,6 +10,11 @@ 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 static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput;
import static sop.testsuite.assertions.SopExecutionAssertions.assertNoSignature;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -21,7 +26,6 @@ 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;
@ -76,60 +80,75 @@ public class VerifyCmdTest {
@Test
public void notAfter_passedDown() throws SOPGPException.UnsupportedOption, ParseException {
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()});
assertSuccess(() ->
SopCLI.execute("verify", "--not-after", "2019-10-29T18:36:45Z",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notAfter(date);
}
@Test
public void notAfter_now() throws SOPGPException.UnsupportedOption {
Date now = new Date();
SopCLI.main(new String[] {"verify", "--not-after", "now", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("verify", "--not-after", "now",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notAfter(dateMatcher(now));
}
@Test
public void notAfter_dashCountsAsEndOfTime() throws SOPGPException.UnsupportedOption {
SopCLI.main(new String[] {"verify", "--not-after", "-", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("verify", "--not-after", "-",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notAfter(AbstractSopCmd.END_OF_TIME);
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void notAfter_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
when(detachedVerify.notAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported."));
SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertUnsupportedOption(() ->
SopCLI.execute("verify", "--not-after", "2019-10-29T18:36:45Z",
signature.getAbsolutePath(), cert.getAbsolutePath()));
}
@Test
public void notBefore_passedDown() throws SOPGPException.UnsupportedOption, ParseException {
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()});
assertSuccess(() ->
SopCLI.execute("verify", "--not-before", "2019-10-29T18:36:45Z",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notBefore(date);
}
@Test
public void notBefore_now() throws SOPGPException.UnsupportedOption {
Date now = new Date();
SopCLI.main(new String[] {"verify", "--not-before", "now", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("verify", "--not-before", "now",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notBefore(dateMatcher(now));
}
@Test
public void notBefore_dashCountsAsBeginningOfTime() throws SOPGPException.UnsupportedOption {
SopCLI.main(new String[] {"verify", "--not-before", "-", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("verify", "--not-before", "-",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notBefore(AbstractSopCmd.BEGINNING_OF_TIME);
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void notBefore_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
when(detachedVerify.notBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported."));
SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertUnsupportedOption(() ->
SopCLI.execute("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()});
assertSuccess(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notAfter(dateMatcher(new Date()));
verify(detachedVerify, times(1)).notBefore(AbstractSopCmd.BEGINNING_OF_TIME);
}
@ -139,43 +158,43 @@ public class VerifyCmdTest {
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void cert_fileNotFoundCausesExit61() {
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), "invalid.asc"});
assertMissingInput(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), "invalid.asc"));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void cert_badDataCausesExit41() throws SOPGPException.BadData, IOException {
when(detachedVerify.cert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertBadData(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void signature_fileNotFoundCausesExit61() {
SopCLI.main(new String[] {"verify", "invalid.sig", cert.getAbsolutePath()});
assertMissingInput(() ->
SopCLI.execute("verify", "invalid.sig", cert.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void signature_badDataCausesExit41() throws SOPGPException.BadData, IOException {
when(detachedVerify.signatures((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertBadData(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.NoSignature.EXIT_CODE)
public void data_noSignaturesCausesExit3() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
when(detachedVerify.data((InputStream) any())).thenThrow(new SOPGPException.NoSignature());
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertNoSignature(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void data_badDataCausesExit41() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
when(detachedVerify.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertBadData(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
}
@Test
@ -192,7 +211,8 @@ public class VerifyCmdTest {
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
assertSuccess(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
System.setOut(originalSout);

View File

@ -4,19 +4,19 @@
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.exception.SOPGPException;
import sop.operation.Version;
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 static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption;
public class VersionCmdTest {
private Version version;
@ -29,6 +29,8 @@ public class VersionCmdTest {
when(version.getVersion()).thenReturn("1.0");
when(version.getExtendedVersion()).thenReturn("MockSop Extended Version Information");
when(version.getBackendVersion()).thenReturn("Foo");
when(version.getSopSpecVersion()).thenReturn("draft-dkg-openpgp-stateless-cli-XX");
when(version.getSopVVersion()).thenReturn("1.0");
when(sop.version()).thenReturn(version);
SopCLI.setSopInstance(sop);
@ -36,26 +38,41 @@ public class VersionCmdTest {
@Test
public void assertVersionCommandWorks() {
SopCLI.main(new String[] {"version"});
assertSuccess(() ->
SopCLI.execute("version"));
verify(version, times(1)).getVersion();
verify(version, times(1)).getName();
}
@Test
public void assertExtendedVersionCommandWorks() {
SopCLI.main(new String[] {"version", "--extended"});
assertSuccess(() ->
SopCLI.execute("version", "--extended"));
verify(version, times(1)).getExtendedVersion();
}
@Test
public void assertBackendVersionCommandWorks() {
SopCLI.main(new String[] {"version", "--backend"});
assertSuccess(() ->
SopCLI.execute("version", "--backend"));
verify(version, times(1)).getBackendVersion();
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void assertSpecVersionCommandWorks() {
assertSuccess(() ->
SopCLI.execute("version", "--sop-spec"));
}
@Test
public void assertSOPVVersionCommandWorks() {
assertSuccess(() ->
SopCLI.execute("version", "--sopv"));
}
@Test
public void assertInvalidOptionResultsInExit37() {
SopCLI.main(new String[] {"version", "--invalid"});
assertUnsupportedOption(() ->
SopCLI.execute("version", "--invalid"));
}
}

View File

@ -9,16 +9,13 @@ import sop.operation.ChangeKeyPassword
import sop.operation.Dearmor
import sop.operation.Decrypt
import sop.operation.DetachedSign
import sop.operation.DetachedVerify
import sop.operation.Encrypt
import sop.operation.ExtractCert
import sop.operation.GenerateKey
import sop.operation.InlineDetach
import sop.operation.InlineSign
import sop.operation.InlineVerify
import sop.operation.ListProfiles
import sop.operation.RevokeKey
import sop.operation.Version
/**
* Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related
@ -26,10 +23,7 @@ import sop.operation.Version
* intended for reuse. If you for example need to generate multiple keys, make a dedicated call to
* [generateKey] once per key generation.
*/
interface SOP {
/** Get information about the implementations name and version. */
fun version(): Version
interface SOP : SOPV {
/** Generate a secret key. */
fun generateKey(): GenerateKey
@ -53,24 +47,6 @@ interface SOP {
*/
fun inlineSign(): InlineSign
/**
* Verify detached signatures. If you need to verify an inline-signed message, use
* [inlineVerify] instead.
*/
fun verify(): DetachedVerify = detachedVerify()
/**
* Verify detached signatures. If you need to verify an inline-signed message, use
* [inlineVerify] instead.
*/
fun detachedVerify(): DetachedVerify
/**
* Verify signatures of an inline-signed message. If you need to verify detached signatures over
* a message, use [detachedVerify] instead.
*/
fun inlineVerify(): InlineVerify
/** Detach signatures from an inline signed message. */
fun inlineDetach(): InlineDetach

View File

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop
import sop.operation.DetachedVerify
import sop.operation.InlineVerify
import sop.operation.Version
/** Subset of [SOP] implementing only OpenPGP signature verification. */
interface SOPV {
/** Get information about the implementations name and version. */
fun version(): Version
/**
* Verify detached signatures. If you need to verify an inline-signed message, use
* [inlineVerify] instead.
*/
fun verify(): DetachedVerify = detachedVerify()
/**
* Verify detached signatures. If you need to verify an inline-signed message, use
* [inlineVerify] instead.
*/
fun detachedVerify(): DetachedVerify
/**
* Verify signatures of an inline-signed message. If you need to verify detached signatures over
* a message, use [detachedVerify] instead.
*/
fun inlineVerify(): InlineVerify
}

View File

@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.enums
@Deprecated("Use of armor labels is deprecated.")
enum class ArmorLabel {
auto,
sig,
key,
cert,
message
}

View File

@ -7,22 +7,10 @@ package sop.operation
import java.io.IOException
import java.io.InputStream
import sop.Ready
import sop.enums.ArmorLabel
import sop.exception.SOPGPException.BadData
import sop.exception.SOPGPException.UnsupportedOption
interface Armor {
/**
* Overrides automatic detection of label.
*
* @param label armor label
* @return builder instance
*/
@Deprecated("Use of armor labels is deprecated and will be removed in a future release.")
@Throws(UnsupportedOption::class)
fun label(label: ArmorLabel): Armor
/**
* Armor the provided data.
*

View File

@ -4,6 +4,9 @@
package sop.operation
import kotlin.jvm.Throws
import sop.exception.SOPGPException
interface Version {
/**
@ -97,4 +100,11 @@ interface Version {
* @return remarks or null
*/
fun getSopSpecImplementationRemarks(): String?
/**
* Return the single-line SEMVER version of the sopv interface subset it provides complete
* coverage of. If the implementation does not provide complete coverage for any sopv interface,
* this method throws an [SOPGPException.UnsupportedOption] instead.
*/
@Throws(SOPGPException.UnsupportedOption::class) fun getSopVVersion(): String
}

View File

@ -15,6 +15,7 @@ import java.io.OutputStream
* class is useful if we need to provide an [OutputStream] at one point in time when the final
* target output stream is not yet known.
*/
@Deprecated("Marked for removal.")
class ProxyOutputStream : OutputStream() {
private val buffer = ByteArrayOutputStream()
private var swapped: OutputStream? = null

View File

@ -0,0 +1,235 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.assertions;
import sop.exception.SOPGPException;
import java.util.function.IntSupplier;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
/**
* DSL for testing the return values of SOP method calls.
*/
public class SopExecutionAssertions {
/**
* Assert that the execution of the given function returns 0.
*
* @param function function to execute
*/
public static void assertSuccess(IntSupplier function) {
assertEquals(0, function.getAsInt());
}
/**
* Assert that the execution of the given function returns a generic error with error code 1.
*
* @param function function to execute.
*/
public static void assertGenericError(IntSupplier function) {
assertEquals(1, function.getAsInt());
}
/**
* Assert that the execution of the given function returns a non-zero error code.
*
* @param function function to execute
*/
public static void assertAnyError(IntSupplier function) {
assertNotEquals(0, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 3
* (which corresponds to {@link sop.exception.SOPGPException.NoSignature}).
*
* @param function function to execute.
*/
public static void assertNoSignature(IntSupplier function) {
assertEquals(SOPGPException.NoSignature.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 13
* (which corresponds to {@link sop.exception.SOPGPException.UnsupportedAsymmetricAlgo}).
*
* @param function function to execute.
*/
public static void assertUnsupportedAsymmetricAlgo(IntSupplier function) {
assertEquals(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 17
* (which corresponds to {@link sop.exception.SOPGPException.CertCannotEncrypt}).
*
* @param function function to execute.
*/
public static void assertCertCannotEncrypt(IntSupplier function) {
assertEquals(SOPGPException.CertCannotEncrypt.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 19
* (which corresponds to {@link sop.exception.SOPGPException.MissingArg}).
*
* @param function function to execute.
*/
public static void assertMissingArg(IntSupplier function) {
assertEquals(SOPGPException.MissingArg.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 23
* (which corresponds to {@link sop.exception.SOPGPException.IncompleteVerification}).
*
* @param function function to execute.
*/
public static void assertIncompleteVerification(IntSupplier function) {
assertEquals(SOPGPException.IncompleteVerification.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 29
* (which corresponds to {@link sop.exception.SOPGPException.CannotDecrypt}).
*
* @param function function to execute.
*/
public static void assertCannotDecrypt(IntSupplier function) {
assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 31
* (which corresponds to {@link sop.exception.SOPGPException.PasswordNotHumanReadable}).
*
* @param function function to execute.
*/
public static void assertPasswordNotHumanReadable(IntSupplier function) {
assertEquals(SOPGPException.PasswordNotHumanReadable.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 37
* (which corresponds to {@link sop.exception.SOPGPException.UnsupportedOption}).
*
* @param function function to execute.
*/
public static void assertUnsupportedOption(IntSupplier function) {
assertEquals(SOPGPException.UnsupportedOption.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 41
* (which corresponds to {@link sop.exception.SOPGPException.BadData}).
*
* @param function function to execute.
*/
public static void assertBadData(IntSupplier function) {
assertEquals(SOPGPException.BadData.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 53
* (which corresponds to {@link sop.exception.SOPGPException.ExpectedText}).
*
* @param function function to execute.
*/
public static void assertExpectedText(IntSupplier function) {
assertEquals(SOPGPException.ExpectedText.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 59
* (which corresponds to {@link sop.exception.SOPGPException.OutputExists}).
*
* @param function function to execute.
*/
public static void assertOutputExists(IntSupplier function) {
assertEquals(SOPGPException.OutputExists.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 61
* (which corresponds to {@link sop.exception.SOPGPException.MissingInput}).
*
* @param function function to execute.
*/
public static void assertMissingInput(IntSupplier function) {
assertEquals(SOPGPException.MissingInput.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 67
* (which corresponds to {@link sop.exception.SOPGPException.KeyIsProtected}).
*
* @param function function to execute.
*/
public static void assertKeyIsProtected(IntSupplier function) {
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 69
* (which corresponds to {@link sop.exception.SOPGPException.UnsupportedSubcommand}).
*
* @param function function to execute.
*/
public static void assertUnsupportedSubcommand(IntSupplier function) {
assertEquals(SOPGPException.UnsupportedSubcommand.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 71
* (which corresponds to {@link sop.exception.SOPGPException.UnsupportedSpecialPrefix}).
*
* @param function function to execute.
*/
public static void assertUnsupportedSpecialPrefix(IntSupplier function) {
assertEquals(SOPGPException.UnsupportedSpecialPrefix.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 73
* (which corresponds to {@link sop.exception.SOPGPException.AmbiguousInput}).
*
* @param function function to execute.
*/
public static void assertAmbiguousInput(IntSupplier function) {
assertEquals(SOPGPException.AmbiguousInput.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 79
* (which corresponds to {@link sop.exception.SOPGPException.KeyCannotSign}).
*
* @param function function to execute.
*/
public static void assertKeyCannotSign(IntSupplier function) {
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 83
* (which corresponds to {@link sop.exception.SOPGPException.IncompatibleOptions}).
*
* @param function function to execute.
*/
public static void assertIncompatibleOptions(IntSupplier function) {
assertEquals(SOPGPException.IncompatibleOptions.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 89
* (which corresponds to {@link sop.exception.SOPGPException.UnsupportedProfile}).
*
* @param function function to execute.
*/
public static void assertUnsupportedProfile(IntSupplier function) {
assertEquals(SOPGPException.UnsupportedProfile.EXIT_CODE, function.getAsInt());
}
}

View File

@ -327,4 +327,15 @@ public class EncryptDecryptTest extends AbstractSOPTest {
.toByteArrayAndResult()
.getBytes());
}
@ParameterizedTest
@MethodSource("provideInstances")
public void passingSecretKeysForPublicKeysFails(SOP sop) {
assertThrows(SOPGPException.BadData.class, () ->
sop.encrypt()
.withCert(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8))
.toByteArrayAndResult()
.getBytes());
}
}

View File

@ -8,14 +8,15 @@ import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.opentest4j.TestAbortedException;
import sop.SOP;
import sop.exception.SOPGPException;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class VersionTest extends AbstractSOPTest {
@ -59,7 +60,7 @@ public class VersionTest extends AbstractSOPTest {
try {
sop.version().getSopSpecVersion();
} catch (RuntimeException e) {
assumeTrue(false); // SOP backend does not support this operation yet
throw new TestAbortedException("SOP backend does not support 'version --sop-spec' yet.");
}
String sopSpec = sop.version().getSopSpecVersion();
@ -72,4 +73,17 @@ public class VersionTest extends AbstractSOPTest {
int sopRevision = sop.version().getSopSpecRevisionNumber();
assertTrue(sop.version().getSopSpecRevisionName().endsWith("" + sopRevision));
}
@ParameterizedTest
@MethodSource("provideInstances")
public void sopVVersionTest(SOP sop) {
try {
sop.version().getSopVVersion();
} catch (SOPGPException.UnsupportedOption e) {
throw new TestAbortedException(
"Implementation does (gracefully) not provide coverage for any sopv interface version.");
} catch (RuntimeException e) {
throw new TestAbortedException("Implementation does not provide coverage for any sopv interface version.");
}
}
}

View File

@ -4,15 +4,15 @@
allprojects {
ext {
shortVersion = '8.0.0'
isSnapshot = false
shortVersion = '10.0.1'
isSnapshot = true
minAndroidSdk = 10
javaSourceCompatibility = 1.8
gsonVersion = '2.10.1'
jsrVersion = '3.0.2'
junitVersion = '5.8.2'
junitSysExitVersion = '1.1.2'
logbackVersion = '1.2.11'
logbackVersion = '1.2.13'
mockitoVersion = '4.5.1'
picocliVersion = '4.6.3'
slf4jVersion = '1.7.36'