diff --git a/.reuse/dep5 b/.reuse/dep5 new file mode 100644 index 0000000..f43556c --- /dev/null +++ b/.reuse/dep5 @@ -0,0 +1,29 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: SOP-Java +Upstream-Contact: Paul Schaub +Source: https://pgpainless.org + +# Sample paragraph, commented out: +# +# Files: src/* +# Copyright: $YEAR $NAME <$CONTACT> +# License: ... + +# Gradle build tool +Files: gradle* +Copyright: 2015 the original author or authors. +License: Apache-2.0 + +# Woodpecker build files +Files: .woodpecker/* +Copyright: 2022 the original author or authors. +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 diff --git a/REUSE.toml b/REUSE.toml deleted file mode 100644 index 7e1b250..0000000 --- a/REUSE.toml +++ /dev/null @@ -1,32 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Paul Schaub -# -# SPDX-License-Identifier: CC0-1.0 - -version = 1 -SPDX-PackageName = "SOP-Java" -SPDX-PackageSupplier = "Paul Schaub " -SPDX-PackageDownloadLocation = "https://pgpainless.org" - -[[annotations]] -path = "gradle**" -precedence = "aggregate" -SPDX-FileCopyrightText = "2015 the original author or authors." -SPDX-License-Identifier = "Apache-2.0" - -[[annotations]] -path = ".woodpecker/**" -precedence = "aggregate" -SPDX-FileCopyrightText = "2022 the original author or authors." -SPDX-License-Identifier = "Apache-2.0" - -[[annotations]] -path = "external-sop/src/main/resources/sop/testsuite/external/**" -precedence = "aggregate" -SPDX-FileCopyrightText = "2023 the original author or authors" -SPDX-License-Identifier = "Apache-2.0" - -[[annotations]] -path = ".github/ISSUE_TEMPLATE/**" -precedence = "aggregate" -SPDX-FileCopyrightText = "2024 the original author or authors" -SPDX-License-Identifier = "Apache-2.0" diff --git a/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt index 99df15e..9aa1d29 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt @@ -21,7 +21,7 @@ class UpdateKeyExternal(binary: String, environment: Properties) : UpdateKey { override fun signingOnly(): UpdateKey = apply { commandList.add("--signing-only") } - override fun noAddedCapabilities(): UpdateKey = apply { commandList.add("--no-added-capabilities") } + override fun noNewMechanisms(): UpdateKey = apply { commandList.add("--no-new-mechanisms") } override fun withKeyPassword(password: ByteArray): UpdateKey = apply { commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCount") diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index 98701fc..943f0f3 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -88,10 +88,7 @@ class SopCLI { // Hide generate-completion command subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true) // render Input/Output sections in help command - subcommands.values - .filter { - (it.getCommand() as Any) is AbstractSopCmd - } // Only for AbstractSopCmd objects + subcommands.values.filter { (it.getCommand() as Any) is AbstractSopCmd } // Only for AbstractSopCmd objects .forEach { (it.getCommand() as AbstractSopCmd).installIORenderer(it) } // overwrite executable name commandName = EXECUTABLE_NAME @@ -99,8 +96,7 @@ class SopCLI { executionExceptionHandler = SOPExecutionExceptionHandler() exitCodeExceptionMapper = SOPExceptionExitCodeMapper() isCaseInsensitiveEnumValuesAllowed = true - } - .execute(*args) + }.execute(*args) } } diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt index 3dcef38..16d56e3 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt @@ -16,7 +16,7 @@ import sop.exception.SOPGPException exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) class MergeCertsCmd : AbstractSopCmd() { - @CommandLine.Option(names = ["--no-armor"], negatable = true) var armor = true + @CommandLine.Option(names = ["--no-armor"], negatable = true) var armor = false @CommandLine.Parameters(paramLabel = "CERTS") var updates: List = listOf() diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt index 931f241..08f9297 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt @@ -20,7 +20,7 @@ class UpdateKeyCmd : AbstractSopCmd() { @Option(names = ["--signing-only"]) var signingOnly = false - @Option(names = ["--no-added-capabilities"]) var noAddedCapabilities = false + @Option(names = ["--no-new-mechanisms"]) var noNewMechanisms = false @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") var withKeyPassword: List = listOf() @@ -38,8 +38,8 @@ class UpdateKeyCmd : AbstractSopCmd() { updateKey.signingOnly() } - if (noAddedCapabilities) { - updateKey.noAddedCapabilities() + if (noNewMechanisms) { + updateKey.noNewMechanisms() } for (passwordFileName in withKeyPassword) { diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt index b83e5a8..9a09a15 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt @@ -5,13 +5,13 @@ package sop.cli.picocli.commands import java.io.IOException -import java.util.* import picocli.CommandLine.Command import picocli.CommandLine.Option import picocli.CommandLine.Parameters import sop.cli.picocli.SopCLI import sop.exception.SOPGPException import sop.util.HexUtil.Companion.bytesToHex +import java.util.* @Command( name = "validate-userid", diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java deleted file mode 100644 index e05923c..0000000 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.operation; - -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 sop.SOP; -import sop.exception.SOPGPException; - -import java.io.IOException; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class CertifyValidateUserIdTest { - - static Stream provideInstances() { - return AbstractSOPTest.provideBackends(); - } - - @ParameterizedTest - @MethodSource("provideInstances") - public void certifyUserId(SOP sop) throws IOException { - byte[] aliceKey = sop.generateKey() - .withKeyPassword("sw0rdf1sh") - .userId("Alice ") - .generate() - .getBytes(); - byte[] aliceCert = sop.extractCert() - .key(aliceKey) - .getBytes(); - - byte[] bobKey = sop.generateKey() - .userId("Bob ") - .generate() - .getBytes(); - byte[] bobCert = sop.extractCert() - .key(bobKey) - .getBytes(); - - // Alice has her own user-id self-certified - assertTrue(sop.validateUserId() - .authorities(aliceCert) - .userId("Alice ") - .subjects(aliceCert), - "Alice accepts her own self-certified user-id"); - - // Alice has not yet certified Bobs user-id - assertFalse(sop.validateUserId() - .authorities(aliceCert) - .userId("Bob ") - .subjects(bobCert), - "Alice has not yet certified Bobs user-id"); - - byte[] bobCertifiedByAlice = sop.certifyUserId() - .userId("Bob ") - .withKeyPassword("sw0rdf1sh") - .keys(aliceKey) - .certs(bobCert) - .getBytes(); - - assertTrue(sop.validateUserId() - .userId("Bob ") - .authorities(aliceCert) - .subjects(bobCertifiedByAlice), - "Alice accepts Bobs user-id after she certified it"); - } - - @ParameterizedTest - @MethodSource("provideInstances") - public void addPetName(SOP sop) throws IOException { - byte[] aliceKey = sop.generateKey() - .userId("Alice ") - .generate() - .getBytes(); - byte[] aliceCert = sop.extractCert() - .key(aliceKey) - .getBytes(); - - byte[] bobKey = sop.generateKey() - .userId("Bob ") - .generate() - .getBytes(); - byte[] bobCert = sop.extractCert() - .key(bobKey) - .getBytes(); - - assertThrows(SOPGPException.CertUserIdNoMatch.class, () -> - sop.certifyUserId() - .userId("Bobby") - .keys(aliceKey) - .certs(bobCert) - .getBytes(), - "Alice cannot create a pet-name for Bob without the --no-require-self-sig flag"); - - byte[] bobWithPetName = sop.certifyUserId() - .userId("Bobby") - .noRequireSelfSig() - .keys(aliceKey) - .certs(bobCert) - .getBytes(); - - assertTrue(sop.validateUserId() - .userId("Bobby") - .authorities(aliceCert) - .subjects(bobWithPetName), - "Alice accepts the pet-name she gave to Bob"); - - assertFalse(sop.validateUserId() - .userId("Bobby") - .authorities(bobWithPetName) - .subjects(bobWithPetName), - "Bob does not accept the pet-name Alice gave him"); - } - - @ParameterizedTest - @MethodSource("provideInstances") - public void certifyWithRevokedKey(SOP sop) throws IOException { - byte[] aliceKey = sop.generateKey() - .userId("Alice ") - .generate() - .getBytes(); - byte[] aliceRevokedCert = sop.revokeKey() - .keys(aliceKey) - .getBytes(); - byte[] aliceRevokedKey = sop.updateKey() - .mergeCerts(aliceRevokedCert) - .key(aliceKey) - .getBytes(); - - byte[] bobKey = sop.generateKey() - .userId("Bob ") - .generate() - .getBytes(); - byte[] bobCert = sop.extractCert() - .key(bobKey) - .getBytes(); - - assertThrows(SOPGPException.KeyCannotCertify.class, () -> - sop.certifyUserId() - .userId("Bob ") - .keys(aliceRevokedKey) - .certs(bobCert) - .getBytes()); - } -} diff --git a/sop-java/src/main/kotlin/sop/SigningResult.kt b/sop-java/src/main/kotlin/sop/SigningResult.kt index 651f8c1..60888e0 100644 --- a/sop-java/src/main/kotlin/sop/SigningResult.kt +++ b/sop-java/src/main/kotlin/sop/SigningResult.kt @@ -9,10 +9,9 @@ package sop * * @param micAlg 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](https://www.rfc-editor.org/rfc/rfc3156#section-5). If more than one signature was - * generated and different digest mechanisms were used, the value of the micalg object is an empty - * string. + * object as described in section 5 of [RFC3156](https://www.rfc-editor.org/rfc/rfc3156#section-5). + * If more than one signature was generated and different digest mechanisms were used, the value + * of the micalg object is an empty string. */ data class SigningResult(val micAlg: MicAlg) { diff --git a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt index 9df1628..862e1bd 100644 --- a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt +++ b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt @@ -16,22 +16,6 @@ abstract class SOPGPException : RuntimeException { abstract fun getExitCode(): Int - /** An otherwise unspecified failure occurred */ - class UnspecificFailure : SOPGPException { - - constructor(message: String) : super(message) - - constructor(message: String, e: Throwable) : super(message, e) - - constructor(e: Throwable) : super(e) - - override fun getExitCode(): Int = EXIT_CODE - - companion object { - const val EXIT_CODE = 1 - } - } - /** No acceptable signatures found (sop verify, inline-verify). */ class NoSignature : SOPGPException { @JvmOverloads @@ -394,23 +378,4 @@ abstract class SOPGPException : RuntimeException { const val EXIT_CODE = 107 } } - - /** - * Key not certification-capable (e.g., expired, revoked, unacceptable usage flags) (sop - * certify-userid) - */ - class KeyCannotCertify : SOPGPException { - - constructor(message: String) : super(message) - - constructor(message: String, e: Throwable) : super(message, e) - - constructor(e: Throwable) : super(e) - - override fun getExitCode(): Int = EXIT_CODE - - companion object { - const val EXIT_CODE = 109 - } - } } diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt index 9a31310..6c32b22 100644 --- a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -19,61 +19,25 @@ interface UpdateKey { */ fun noArmor(): UpdateKey - /** - * Allow key to be used for signing only. - * If this option is not present, the operation may add a new, encryption-capable component key. - */ @Throws(SOPGPException.UnsupportedOption::class) fun signingOnly(): UpdateKey - /** - * Do not allow adding new capabilities to the key. - * If this option is not present, the operation may add support for new capabilities to the key. - */ - @Throws(SOPGPException.UnsupportedOption::class) fun noAddedCapabilities(): UpdateKey + @Throws(SOPGPException.UnsupportedOption::class) fun noNewMechanisms(): UpdateKey - /** - * Provide a passphrase for unlocking the secret key. - * - * @param password password - */ @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) fun withKeyPassword(password: String): UpdateKey = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) - /** - * Provide a passphrase for unlocking the secret key. - * - * @param password password - */ @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) fun withKeyPassword(password: ByteArray): UpdateKey - /** - * Provide certificates that might contain updated signatures or third-party certifications. - * These certificates will be merged into the key. - * - * @param certs input stream of certificates - */ @Throws( SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) fun mergeCerts(certs: InputStream): UpdateKey - /** - * Provide certificates that might contain updated signatures or third-party certifications. - * These certificates will be merged into the key. - * - * @param certs binary certificates - */ @Throws( SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) fun mergeCerts(certs: ByteArray): UpdateKey = mergeCerts(certs.inputStream()) - /** - * Provide the OpenPGP key to update. - * - * @param key input stream containing the key - * @return handle to acquire the updated OpenPGP key from - */ @Throws( SOPGPException.BadData::class, IOException::class, @@ -81,12 +45,6 @@ interface UpdateKey { SOPGPException.PrimaryKeyBad::class) fun key(key: InputStream): Ready - /** - * Provide the OpenPGP key to update. - * - * @param key binary OpenPGP key - * @return handle to acquire the updated OpenPGP key from - */ @Throws( SOPGPException.BadData::class, IOException::class, diff --git a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt index fe20fd4..fb8cab6 100644 --- a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt +++ b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt @@ -6,8 +6,8 @@ package sop.operation import java.io.IOException import java.io.InputStream -import java.util.* import sop.exception.SOPGPException +import java.util.* /** Subcommand to validate UserIDs on certificates. */ interface ValidateUserId {