From dc92f0b62308a11dfd81893a415c600faeabee46 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Sep 2024 22:40:36 +0200 Subject: [PATCH 01/29] Add new exceptions --- .../src/main/resources/msg_sop.properties | 2 ++ .../src/main/resources/msg_sop_de.properties | 2 ++ .../kotlin/sop/exception/SOPGPException.kt | 30 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 94e4dc0..8179676 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -38,6 +38,8 @@ usage.exitCodeList.19=83:Options were supplied that are incompatible with each o usage.exitCodeList.20=89:The requested profile is unsupported, or the indicated subcommand does not accept profiles usage.exitCodeList.21=97:The implementation supports some form of hardware-backed secret keys, but could not identify the hardware device usage.exitCodeList.22=101:The implementation tried to use a hardware-backed secret key, but the cryptographic hardware refused the operation for some reason other than a bad PIN or password +usage.exitCodeList.23=103:The primary key of a KEYS object is too weak or revoked +usage.exitCodeList.24=107:The CERTS object has no matching User ID ## SHARED RESOURCES stacktrace=Print stacktrace diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 786fa36..0538cd9 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -38,6 +38,8 @@ usage.exitCodeList.19=83:Miteinander inkompatible Optionen spezifiziert usage.exitCodeList.20=89:Das angeforderte Profil wird nicht unterstützt, oder der angegebene Unterbefehl akzeptiert keine Profile usage.exitCodeList.21=97:Die Anwendung unterstützt hardwaregestützte private Schlüssel, aber kann das Gerät nicht identifizieren usage.exitCodeList.22=101:Die Anwendung versuchte, einen hardwaregestützten Schlüssel zu verwenden, aber das Gerät lehnte den Vorgang aus einem anderen Grund als einer falschen PIN oder einem falschen Passwort ab +usage.exitCodeList.23=103:Der primäre private Schlüssel ist zu schwach oder widerrufen +usage.exitCodeList.24=107:Das Zertifikat hat keine übereinstimmende User ID ## SHARED RESOURCES stacktrace=Stacktrace ausgeben diff --git a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt index bc9131f..1f9ce6b 100644 --- a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt +++ b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt @@ -337,4 +337,34 @@ abstract class SOPGPException : RuntimeException { const val EXIT_CODE = 101 } } + + /** + * The primary key of a KEYS object is too weak or revoked. + */ + class PrimaryKeyBad : SOPGPException { + constructor() : super() + + constructor(errorMsg: String) : super(errorMsg) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 103 + } + } + + /** + * The CERTS object has no matching User ID. + */ + class CertUserIdNoMatch : SOPGPException { + constructor() : super() + + constructor(errorMsg: String) : super(errorMsg) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 107 + } + } } From b9964339d173c22c894ae1d3b468e270fcf73e44 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Sep 2024 22:43:36 +0200 Subject: [PATCH 02/29] Add implementation of update-key command --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 1 + .../sop/cli/picocli/commands/UpdateKeyCmd.kt | 76 +++++++++++++++++++ .../main/resources/msg_update-key.properties | 19 +++++ .../resources/msg_update-key_de.properties | 18 +++++ sop-java/src/main/kotlin/sop/SOP.kt | 18 ++--- .../main/kotlin/sop/operation/UpdateKey.kt | 43 +++++++++++ 6 files changed, 163 insertions(+), 12 deletions(-) create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt create mode 100644 sop-java-picocli/src/main/resources/msg_update-key.properties create mode 100644 sop-java-picocli/src/main/resources/msg_update-key_de.properties create mode 100644 sop-java/src/main/kotlin/sop/operation/UpdateKey.kt 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 62065f4..df8e883 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 @@ -27,6 +27,7 @@ import sop.exception.SOPGPException ChangeKeyPasswordCmd::class, RevokeKeyCmd::class, ExtractCertCmd::class, + UpdateKeyCmd::class, // Messaging subcommands SignCmd::class, VerifyCmd::class, 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 new file mode 100644 index 0000000..2afa015 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import picocli.CommandLine.Command +import picocli.CommandLine.Option +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException.* +import java.io.IOException + +@Command( + name = "update-key", + resourceBundle = "msg_update-key", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class UpdateKeyCmd : AbstractSopCmd() { + + @Option(names = ["--no-armor"], negatable = true) var armor = true + + @Option(names = ["--signing-only"]) var signingOnly = false + + @Option(names = ["--no-new-mechanisms"]) var noNewMechanisms = false + + @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") + var withKeyPassword: List = listOf() + + @Option(names = ["--merge-certs"], paramLabel = "CERTS") + var mergeCerts: List = listOf() + + override fun run() { + val updateKey = throwIfUnsupportedSubcommand(SopCLI.getSop().updateKey(), "update-key") + + if (!armor) { + updateKey.noArmor() + } + + if (signingOnly) { + updateKey.signingOnly() + } + + if (noNewMechanisms) { + updateKey.noNewMechanisms() + } + + for (passwordFileName in withKeyPassword) { + try { + val password = stringFromInputStream(getInput(passwordFileName)) + updateKey.withKeyPassword(password) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + throw UnsupportedOption(errorMsg, unsupportedOption) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + for (certInput in mergeCerts) { + try { + getInput(certInput).use { certIn -> updateKey.mergeCerts(certIn) } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_certificate", certInput) + throw BadData(errorMsg, badData) + } + } + + try { + val ready = updateKey.key(System.`in`) + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} \ No newline at end of file diff --git a/sop-java-picocli/src/main/resources/msg_update-key.properties b/sop-java-picocli/src/main/resources/msg_update-key.properties new file mode 100644 index 0000000..dd4446d --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_update-key.properties @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Keep a secret key up-to-date +no-armor=ASCII armor the output +signing-only=TODO: Document +no-new-mechanisms=Do not add feature support for new mechanisms, which the key did not previously support +with-key-password.0=Passphrase to unlock the secret key(s). +with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +merge-certs.0=Merge additional elements found in the corresponding CERTS objects into the updated secret keys +merge-certs.1=This can be used, for example, to absorb a third-party certification into the Transferable Secret Key + +stacktrace=Print stacktrace +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_update-key_de.properties b/sop-java-picocli/src/main/resources/msg_update-key_de.properties new file mode 100644 index 0000000..86b999e --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_update-key_de.properties @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Halte einen Schlüssel auf dem neusten Stand +no-armor=Schütze Ausgabe mit ASCII Armor +signing-only=TODO: Dokumentieren +no-new-mechanisms=Füge keine neuen Funktionen hinzu, die der Schlüssel nicht bereits zuvor unterstützt hat +with-key-password.0=Passwort zum Entsperren der privaten Schlüssel +with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +merge-certs.0=Führe zusätzliche Elemente aus entsprechenden CERTS Objekten mit dem privaten Schlüssel zusammen +merge-certs.1=Dies kann zum Beispiel dazu genutzt werden, Zertifizierungen dritter in den privaten Schlüssel zu übernehmen + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index 7fdd414..c53bb7d 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -4,18 +4,7 @@ package sop -import sop.operation.Armor -import sop.operation.ChangeKeyPassword -import sop.operation.Dearmor -import sop.operation.Decrypt -import sop.operation.DetachedSign -import sop.operation.Encrypt -import sop.operation.ExtractCert -import sop.operation.GenerateKey -import sop.operation.InlineDetach -import sop.operation.InlineSign -import sop.operation.ListProfiles -import sop.operation.RevokeKey +import sop.operation.* /** * Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related @@ -70,4 +59,9 @@ interface SOP : SOPV { /** Update a key's password. */ fun changeKeyPassword(): ChangeKeyPassword + + /** + * Keep a secret key up-to-date. + */ + fun updateKey(): UpdateKey } diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt new file mode 100644 index 0000000..1b12f6f --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import sop.Ready +import sop.exception.SOPGPException +import sop.util.UTF8Util +import java.io.IOException +import java.io.InputStream + +interface UpdateKey { + + /** + * Disable ASCII armor encoding of the output. + * + * @return builder instance + */ + fun noArmor(): UpdateKey + + @Throws(SOPGPException.UnsupportedOption::class) fun signingOnly(): UpdateKey + + @Throws(SOPGPException.UnsupportedOption::class) fun noNewMechanisms(): UpdateKey + + @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + fun withKeyPassword(password: String): UpdateKey = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + + @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + fun withKeyPassword(password: ByteArray): UpdateKey + + @Throws(SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) + fun mergeCerts(certs: InputStream): UpdateKey + + @Throws(SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) + fun mergeCerts(certs: ByteArray): UpdateKey = mergeCerts(certs.inputStream()) + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class, SOPGPException.PrimaryKeyBad::class) + fun key(key: InputStream): Ready + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class, SOPGPException.PrimaryKeyBad::class) + fun key(key: ByteArray): Ready = key(key.inputStream()) +} \ No newline at end of file From 03cb8d70f93eef491ac49a462b68bf5c1dd68c95 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Sep 2024 22:43:50 +0200 Subject: [PATCH 03/29] Add implementation of merge-certs command --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 1 + .../sop/cli/picocli/commands/MergeCertsCmd.kt | 48 +++++++++++++++++++ .../main/resources/msg_merge-certs.properties | 15 ++++++ .../resources/msg_merge-certs_de.properties | 19 ++++++++ sop-java/src/main/kotlin/sop/SOP.kt | 5 ++ .../main/kotlin/sop/operation/MergeCerts.kt | 28 +++++++++++ 6 files changed, 116 insertions(+) create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt create mode 100644 sop-java-picocli/src/main/resources/msg_merge-certs.properties create mode 100644 sop-java-picocli/src/main/resources/msg_merge-certs_de.properties create mode 100644 sop-java/src/main/kotlin/sop/operation/MergeCerts.kt 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 df8e883..9d1a305 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 @@ -28,6 +28,7 @@ import sop.exception.SOPGPException RevokeKeyCmd::class, ExtractCertCmd::class, UpdateKeyCmd::class, + MergeCertsCmd::class, // Messaging subcommands SignCmd::class, VerifyCmd::class, 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 new file mode 100644 index 0000000..15b33f8 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import picocli.CommandLine +import picocli.CommandLine.Command +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException +import java.io.IOException + +@Command( + name = "merge-certs", + resourceBundle = "msg_merge-certs", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +class MergeCertsCmd : AbstractSopCmd() { + + @CommandLine.Option(names = ["--no-armor"], negatable = true) + var armor = false + + @CommandLine.Parameters(paramLabel = "CERTS") + var updates: List = listOf() + + override fun run() { + val mergeCerts = throwIfUnsupportedSubcommand(SopCLI.getSop().mergeCerts(), "merge-certs") + + if (!armor) { + mergeCerts.noArmor() + } + + for (certFileName in updates) { + try { + getInput(certFileName).use { mergeCerts.updates(it) } + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + try { + + val ready = mergeCerts.baseCertificates(System.`in`) + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} \ No newline at end of file diff --git a/sop-java-picocli/src/main/resources/msg_merge-certs.properties b/sop-java-picocli/src/main/resources/msg_merge-certs.properties new file mode 100644 index 0000000..866db4b --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_merge-certs.properties @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.headerHeading=Merge OpenPGP certificates%n +usage.description=BLABLA +no-armor=ASCII armor the output +CERTS[0..*]=OpenPGP certificates from which updates shall be merged into the base certificates from standard input + +stacktrace=Print stacktrace +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties b/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties new file mode 100644 index 0000000..021c535 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.headerHeading=OpenPGP Zertifikate zusammenführen%n%n +usage.header=Führe OpenPGP Zertifikate aus der Standardeingabe mit ensprechenden Elementen aus CERTS zusammen und gebe das Ergebnis auf der Standardausgabe aus +usage.description=Es werden nur Zertifikate auf die Standardausgabe geschrieben, welche Teil der Standardeingabe waren +no-armor=Schütze Ausgabe mit ASCII Armor +CERTS[0..*]=OpenPGP Zertifikate aus denen neue Elemente in die Basiszertifikate aus der Standardeingabe übernommen werden sollen + +usage.parameterList.0=STANDARDIN +usage.parameterList.1=STANDARDOUT + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n +usage.synopsisHeading=Aufruf:\u0020 +usage.descriptionHeading=%nHinweise:%n +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index c53bb7d..1640d5f 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -64,4 +64,9 @@ interface SOP : SOPV { * Keep a secret key up-to-date. */ fun updateKey(): UpdateKey + + /** + * Merge OpenPGP certificates. + */ + fun mergeCerts(): MergeCerts } diff --git a/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt b/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt new file mode 100644 index 0000000..f60d291 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import sop.Ready +import sop.exception.SOPGPException +import java.io.IOException +import java.io.InputStream + +interface MergeCerts { + + @Throws(SOPGPException.UnsupportedOption::class) + fun noArmor(): MergeCerts + + @Throws(SOPGPException.BadData::class, IOException::class) + fun updates(updateCerts: InputStream): MergeCerts + + @Throws(SOPGPException.BadData::class, IOException::class) + fun updates(updateCerts: ByteArray): MergeCerts = updates(updateCerts.inputStream()) + + @Throws(SOPGPException.BadData::class, IOException::class) + fun baseCertificates(certs: InputStream): Ready + + @Throws(SOPGPException.BadData::class, IOException::class) + fun baseCertificates(certs: ByteArray): Ready = baseCertificates(certs.inputStream()) +} \ No newline at end of file From 7e9a8f61cb8fb4525da3c9b8c376c90e0654a6ee Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 18 Sep 2024 15:50:17 +0200 Subject: [PATCH 04/29] Add support for rendering help info for input and output --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 8 +- .../cli/picocli/commands/AbstractSopCmd.kt | 100 ++++++++++++++++++ .../src/main/resources/msg_sop.properties | 7 +- .../src/main/resources/msg_sop_de.properties | 5 +- 4 files changed, 115 insertions(+), 5 deletions(-) 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 9d1a305..4db6b30 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 @@ -83,16 +83,20 @@ class SopCLI { return CommandLine(SopCLI::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) + // render Input/Output sections in help command + 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 // setup exception handling executionExceptionHandler = SOPExecutionExceptionHandler() exitCodeExceptionMapper = SOPExceptionExitCodeMapper() isCaseInsensitiveEnumValuesAllowed = true - } - .execute(*args) + }.execute(*args) } } diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt index 4629e57..65be1be 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt @@ -7,6 +7,11 @@ package sop.cli.picocli.commands import java.io.* import java.text.ParseException import java.util.* +import picocli.CommandLine +import picocli.CommandLine.Help +import picocli.CommandLine.Help.Column +import picocli.CommandLine.Help.TextTable +import picocli.CommandLine.IHelpSectionRenderer import sop.cli.picocli.commands.AbstractSopCmd.EnvironmentVariableResolver import sop.exception.SOPGPException.* import sop.util.UTCUtil.Companion.parseUTCDate @@ -215,11 +220,106 @@ abstract class AbstractSopCmd(locale: Locale = Locale.getDefault()) : Runnable { } } + /** + * See + * [Example](https://github.com/remkop/picocli/blob/main/picocli-examples/src/main/java/picocli/examples/customhelp/EnvironmentVariablesSection.java) + */ + class InputOutputHelpSectionRenderer(private val argument: Pair) : + IHelpSectionRenderer { + + override fun render(help: Help): String { + return argument.let { + val calcLen = + help.calcLongOptionColumnWidth( + help.commandSpec().options(), + help.commandSpec().positionalParameters(), + help.colorScheme()) + val keyLength = + help + .commandSpec() + .usageMessage() + .longOptionsMaxWidth() + .coerceAtMost(calcLen - 1) + val table = + TextTable.forColumns( + help.colorScheme(), + Column(keyLength + 7, 6, Column.Overflow.SPAN), + Column(width(help) - (keyLength + 7), 0, Column.Overflow.WRAP)) + table.setAdjustLineBreaksForWideCJKCharacters(adjustCJK(help)) + table.addRowValues("@|yellow ${argument.first}|@", argument.second ?: "") + table.toString() + } + } + + private fun adjustCJK(help: Help) = + help.commandSpec().usageMessage().adjustLineBreaksForWideCJKCharacters() + + private fun width(help: Help) = help.commandSpec().usageMessage().width() + } + + fun installIORenderer(cmd: CommandLine) { + val inputName = getResString(cmd, "standardInput") + if (inputName != null) { + cmd.helpSectionMap[SECTION_KEY_STANDARD_INPUT_HEADING] = IHelpSectionRenderer { + getResString(cmd, "standardInputHeading") + } + cmd.helpSectionMap[SECTION_KEY_STANDARD_INPUT_DETAILS] = + InputOutputHelpSectionRenderer( + inputName to getResString(cmd, "standardInputDescription")) + cmd.helpSectionKeys = + insertKey( + cmd.helpSectionKeys, + SECTION_KEY_STANDARD_INPUT_HEADING, + SECTION_KEY_STANDARD_INPUT_DETAILS) + } + + val outputName = getResString(cmd, "standardOutput") + if (outputName != null) { + cmd.helpSectionMap[SECTION_KEY_STANDARD_OUTPUT_HEADING] = IHelpSectionRenderer { + getResString(cmd, "standardOutputHeading") + } + cmd.helpSectionMap[SECTION_KEY_STANDARD_OUTPUT_DETAILS] = + InputOutputHelpSectionRenderer( + outputName to getResString(cmd, "standardOutputDescription")) + cmd.helpSectionKeys = + insertKey( + cmd.helpSectionKeys, + SECTION_KEY_STANDARD_OUTPUT_HEADING, + SECTION_KEY_STANDARD_OUTPUT_DETAILS) + } + } + + private fun insertKey(keys: List, header: String, details: String): List { + val index = + keys.indexOf(CommandLine.Model.UsageMessageSpec.SECTION_KEY_EXIT_CODE_LIST_HEADING) + val result = keys.toMutableList() + result.add(index, header) + result.add(index + 1, details) + return result + } + + private fun getResString(cmd: CommandLine, key: String): String? = + try { + cmd.resourceBundle.getString(key) + } catch (m: MissingResourceException) { + try { + cmd.parent.resourceBundle.getString(key) + } catch (m: MissingResourceException) { + null + } + } + ?.let { String.format(it) } + companion object { const val PRFX_ENV = "@ENV:" const val PRFX_FD = "@FD:" + const val SECTION_KEY_STANDARD_INPUT_HEADING = "standardInputHeading" + const val SECTION_KEY_STANDARD_INPUT_DETAILS = "standardInput" + const val SECTION_KEY_STANDARD_OUTPUT_HEADING = "standardOutputHeading" + const val SECTION_KEY_STANDARD_OUTPUT_DETAILS = "standardOutput" + @JvmField val DAWN_OF_TIME = Date(0) @JvmField diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 8179676..d5d997a 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -9,10 +9,13 @@ locale=Locale for description texts # Generic usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n +standardInputHeading=%nInput:%n +standardOutputHeading=%nOutput:%n + # Exit Codes usage.exitCodeListHeading=%nExit Codes:%n usage.exitCodeList.0=\u00200:Successful program execution diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 0538cd9..73efe89 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -10,9 +10,12 @@ locale=Gebietsschema f # Generic usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n +standardInputHeading=%nEingabe:%n +standardOutputHeading=%nAusgabe:%n + # Exit Codes usage.exitCodeListHeading=%nExit Codes:%n usage.exitCodeList.0=\u00200:Erfolgreiche Programmausführung From c48a17422f4cad8622177581366ca60a9295b6fb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 18 Sep 2024 16:01:30 +0200 Subject: [PATCH 05/29] Implement certify-userid command --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 1 + .../cli/picocli/commands/CertifyUserIdCmd.kt | 87 +++++++++++++++++++ .../resources/msg_certify-userid.properties | 23 +++++ .../msg_certify-userid_de.properties | 20 +++++ sop-java/src/main/kotlin/sop/SOP.kt | 5 ++ .../kotlin/sop/operation/CertifyUserId.kt | 41 +++++++++ 6 files changed, 177 insertions(+) create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt create mode 100644 sop-java-picocli/src/main/resources/msg_certify-userid.properties create mode 100644 sop-java-picocli/src/main/resources/msg_certify-userid_de.properties create mode 100644 sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt 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 4db6b30..d234e54 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 @@ -29,6 +29,7 @@ import sop.exception.SOPGPException ExtractCertCmd::class, UpdateKeyCmd::class, MergeCertsCmd::class, + CertifyUserIdCmd::class, // Messaging subcommands SignCmd::class, VerifyCmd::class, diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt new file mode 100644 index 0000000..71ef79f --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.Command +import picocli.CommandLine.Model.CommandSpec +import picocli.CommandLine.Option +import picocli.CommandLine.Parameters +import picocli.CommandLine.Spec +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.UnsupportedOption + +@Command( + name = "certify-userid", + resourceBundle = "msg_certify-userid", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class CertifyUserIdCmd : AbstractSopCmd() { + + @Spec var spec: CommandSpec? = null + + @Option(names = ["--no-armor"], negatable = true) var armor = true + + @Option(names = ["--userid"], required = true, arity = "1..*", paramLabel = "USERID") + var userIds: List = listOf() + + @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") + var withKeyPassword: List = listOf() + + @Option(names = ["--no-require-self-sig"]) var noRequireSelfSig = false + + @Parameters(paramLabel = "KEYS", arity = "1..*") var keys: List = listOf() + + override fun run() { + val certifyUserId = + throwIfUnsupportedSubcommand(SopCLI.getSop().certifyUserId(), "certify-userid") + + if (!armor) { + certifyUserId.noArmor() + } + + if (noRequireSelfSig) { + certifyUserId.noRequireSelfSig() + } + + for (userId in userIds) { + certifyUserId.userId(userId) + } + + for (passwordFileName in withKeyPassword) { + try { + val password = stringFromInputStream(getInput(passwordFileName)) + certifyUserId.withKeyPassword(password) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + throw UnsupportedOption(errorMsg, unsupportedOption) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + for (keyInput in keys) { + try { + getInput(keyInput).use { certifyUserId.keys(it) } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_private_key", keyInput) + throw BadData(errorMsg, badData) + } + } + + try { + val ready = certifyUserId.certs(System.`in`) + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_private_key", "STDIN") + throw BadData(errorMsg, badData) + } + } +} diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid.properties b/sop-java-picocli/src/main/resources/msg_certify-userid.properties new file mode 100644 index 0000000..5eb7aa3 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_certify-userid.properties @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Certify OpenPGP Certificate User IDs +no-armor=ASCII armor the output +userid=Identities that shall be certified +with-key-password.0=Passphrase to unlock the secret key(s). +with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +no-require-self-sig=Certify the UserID regardless of whether self-certifications are present +KEYS[0..*]=Private keys + +standardInput=CERTS +standardInputDescription=Certificates that shall be certified +standardOutput=CERTS +standardOutputDescription=Certified certificates + +stacktrace=Print stacktrace +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=Parameters:%n +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = Commands:%n +usage.optionListHeading = Options:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties new file mode 100644 index 0000000..0237fa6 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Zertifiziere OpenPGP Zertifikat Identitäten +no-armor=Schütze Ausgabe mit ASCII Armor +userid=Identität, die zertifiziert werden soll +with-key-password.0=Passwort zum Entsperren der privaten Schlüssel +with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +no-require-self-sig=Zertifiziere die Identität, unabhängig davon, ob eine Selbstzertifizierung vorhanden ist +KEYS[0..*]=Private Schlüssel + +standardInputDescription=Zertifikate, auf denen Identitäten zertifiziert werden sollen +standardOutputDescription=Zertifizierte Zertifikate + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=Parameter:%n +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=Befehle:%n +usage.optionListHeading = Optionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index 1640d5f..5435cad 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -69,4 +69,9 @@ interface SOP : SOPV { * Merge OpenPGP certificates. */ fun mergeCerts(): MergeCerts + + /** + * Certify OpenPGP Certificate User-IDs. + */ + fun certifyUserId(): CertifyUserId } diff --git a/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt b/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt new file mode 100644 index 0000000..92fff20 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import sop.Ready +import sop.exception.SOPGPException +import sop.util.UTF8Util +import java.io.IOException +import java.io.InputStream + +interface CertifyUserId { + + @Throws(SOPGPException.UnsupportedOption::class) + fun noArmor(): CertifyUserId + + @Throws(SOPGPException.UnsupportedOption::class) + fun userId(userId: String): CertifyUserId + + @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + fun withKeyPassword(password: String): CertifyUserId = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + + @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + fun withKeyPassword(password: ByteArray): CertifyUserId + + @Throws(SOPGPException.UnsupportedOption::class) + fun noRequireSelfSig(): CertifyUserId + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class) + fun keys(keys: InputStream): CertifyUserId + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class) + fun keys(keys: ByteArray): CertifyUserId = keys(keys.inputStream()) + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + fun certs(certs: InputStream): Ready + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + fun certs(certs: ByteArray): Ready = certs(certs.inputStream()) +} \ No newline at end of file From 8f7a085911201ffc40b7007018a2010daecdd202 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 18 Sep 2024 16:01:58 +0200 Subject: [PATCH 06/29] Checkstyle and exception handling improvements --- .../sop/cli/picocli/commands/MergeCertsCmd.kt | 10 ++++------ .../sop/cli/picocli/commands/UpdateKeyCmd.kt | 15 +++++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) 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 15b33f8..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 @@ -4,11 +4,11 @@ package sop.cli.picocli.commands +import java.io.IOException import picocli.CommandLine import picocli.CommandLine.Command import sop.cli.picocli.SopCLI import sop.exception.SOPGPException -import java.io.IOException @Command( name = "merge-certs", @@ -16,11 +16,9 @@ import java.io.IOException exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) class MergeCertsCmd : AbstractSopCmd() { - @CommandLine.Option(names = ["--no-armor"], negatable = true) - var armor = false + @CommandLine.Option(names = ["--no-armor"], negatable = true) var armor = false - @CommandLine.Parameters(paramLabel = "CERTS") - var updates: List = listOf() + @CommandLine.Parameters(paramLabel = "CERTS") var updates: List = listOf() override fun run() { val mergeCerts = throwIfUnsupportedSubcommand(SopCLI.getSop().mergeCerts(), "merge-certs") @@ -45,4 +43,4 @@ class MergeCertsCmd : AbstractSopCmd() { throw RuntimeException(e) } } -} \ No newline at end of file +} 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 2afa015..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 @@ -4,11 +4,11 @@ package sop.cli.picocli.commands +import java.io.IOException import picocli.CommandLine.Command import picocli.CommandLine.Option import sop.cli.picocli.SopCLI import sop.exception.SOPGPException.* -import java.io.IOException @Command( name = "update-key", @@ -25,8 +25,7 @@ class UpdateKeyCmd : AbstractSopCmd() { @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") var withKeyPassword: List = listOf() - @Option(names = ["--merge-certs"], paramLabel = "CERTS") - var mergeCerts: List = listOf() + @Option(names = ["--merge-certs"], paramLabel = "CERTS") var mergeCerts: List = listOf() override fun run() { val updateKey = throwIfUnsupportedSubcommand(SopCLI.getSop().updateKey(), "update-key") @@ -48,7 +47,8 @@ class UpdateKeyCmd : AbstractSopCmd() { val password = stringFromInputStream(getInput(passwordFileName)) updateKey.withKeyPassword(password) } catch (unsupportedOption: UnsupportedOption) { - val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") throw UnsupportedOption(errorMsg, unsupportedOption) } catch (e: IOException) { throw RuntimeException(e) @@ -57,7 +57,7 @@ class UpdateKeyCmd : AbstractSopCmd() { for (certInput in mergeCerts) { try { - getInput(certInput).use { certIn -> updateKey.mergeCerts(certIn) } + getInput(certInput).use { updateKey.mergeCerts(it) } } catch (e: IOException) { throw RuntimeException(e) } catch (badData: BadData) { @@ -71,6 +71,9 @@ class UpdateKeyCmd : AbstractSopCmd() { ready.writeTo(System.out) } catch (e: IOException) { throw RuntimeException(e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_private_key", "STDIN") + throw BadData(errorMsg, badData) } } -} \ No newline at end of file +} From d9afc3f2a0e20a7c3d857c00bb0dcf75fd32488a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 18 Sep 2024 16:56:26 +0200 Subject: [PATCH 07/29] Update msg files with input/output information --- .../src/main/resources/msg_armor.properties | 8 ++++++-- .../src/main/resources/msg_armor_de.properties | 6 ++++-- .../src/main/resources/msg_certify-userid.properties | 6 +++--- .../main/resources/msg_certify-userid_de.properties | 6 +++--- .../main/resources/msg_change-key-password.properties | 9 +++++++-- .../resources/msg_change-key-password_de.properties | 5 ++++- .../src/main/resources/msg_dearmor.properties | 9 +++++++-- .../src/main/resources/msg_dearmor_de.properties | 5 ++++- .../src/main/resources/msg_decrypt.properties | 9 +++++++-- .../src/main/resources/msg_decrypt_de.properties | 5 ++++- .../src/main/resources/msg_detached-sign.properties | 9 +++++++-- .../main/resources/msg_detached-sign_de.properties | 5 ++++- .../src/main/resources/msg_detached-verify.properties | 9 +++++++-- .../main/resources/msg_detached-verify_de.properties | 5 ++++- .../src/main/resources/msg_encrypt.properties | 9 +++++++-- .../src/main/resources/msg_encrypt_de.properties | 5 ++++- .../src/main/resources/msg_extract-cert.properties | 9 +++++++-- .../src/main/resources/msg_extract-cert_de.properties | 5 ++++- .../src/main/resources/msg_generate-key.properties | 7 +++++-- .../src/main/resources/msg_generate-key_de.properties | 4 +++- .../src/main/resources/msg_help.properties | 4 ++-- .../src/main/resources/msg_help_de.properties | 2 +- .../src/main/resources/msg_inline-detach.properties | 9 +++++++-- .../main/resources/msg_inline-detach_de.properties | 5 ++++- .../src/main/resources/msg_inline-sign.properties | 9 +++++++-- .../src/main/resources/msg_inline-sign_de.properties | 5 ++++- .../src/main/resources/msg_inline-verify.properties | 9 +++++++-- .../main/resources/msg_inline-verify_de.properties | 5 ++++- .../src/main/resources/msg_list-profiles.properties | 7 +++++-- .../main/resources/msg_list-profiles_de.properties | 4 +++- .../src/main/resources/msg_merge-certs.properties | 10 ++++++++-- .../src/main/resources/msg_merge-certs_de.properties | 10 +++++----- .../src/main/resources/msg_revoke-key.properties | 11 ++++++++--- .../src/main/resources/msg_revoke-key_de.properties | 5 ++++- .../src/main/resources/msg_sop.properties | 1 + .../src/main/resources/msg_sop_de.properties | 1 + .../src/main/resources/msg_update-key.properties | 9 +++++++-- .../src/main/resources/msg_update-key_de.properties | 5 ++++- .../src/main/resources/msg_version.properties | 6 ++++-- .../src/main/resources/msg_version_de.properties | 4 +++- 40 files changed, 190 insertions(+), 66 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_armor.properties b/sop-java-picocli/src/main/resources/msg_armor.properties index b4dcb59..1b7c1fb 100644 --- a/sop-java-picocli/src/main/resources/msg_armor.properties +++ b/sop-java-picocli/src/main/resources/msg_armor.properties @@ -3,9 +3,13 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Add ASCII Armor to standard input +standardInput=BINARY +standardInputDescription=OpenPGP material (SIGNATURES, KEYS, CERTS, CIPHERTEXT, INLINESIGNED) +standardOutput=ARMORED +standardOutputDescription=Same material, but with ASCII-armoring added, if not already present + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_armor_de.properties b/sop-java-picocli/src/main/resources/msg_armor_de.properties index 4c365a8..34383c8 100644 --- a/sop-java-picocli/src/main/resources/msg_armor_de.properties +++ b/sop-java-picocli/src/main/resources/msg_armor_de.properties @@ -3,9 +3,11 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Schütze Standard-Eingabe mit ASCII Armor +standardInputDescription=OpenPGP Material (SIGNATURES, KEYS, CERTS, CIPHERTEXT, INLINESIGNED) +standardOutputDescription=Dasselbe Material, aber mit ASCII Armor kodiert, falls noch nicht geschehen + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 -usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid.properties b/sop-java-picocli/src/main/resources/msg_certify-userid.properties index 5eb7aa3..252aae4 100644 --- a/sop-java-picocli/src/main/resources/msg_certify-userid.properties +++ b/sop-java-picocli/src/main/resources/msg_certify-userid.properties @@ -16,8 +16,8 @@ standardOutputDescription=Certified certificates stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 -usage.parameterListHeading=Parameters:%n +usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = Commands:%n -usage.optionListHeading = Options:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties index 0237fa6..9f0a673 100644 --- a/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties +++ b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties @@ -13,8 +13,8 @@ standardInputDescription=Zertifikate, auf denen Identit standardOutputDescription=Zertifizierte Zertifikate # Generic TODO: Remove when bumping picocli to 4.7.0 -usage.parameterListHeading=Parameter:%n +usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 -usage.commandListHeading=Befehle:%n -usage.optionListHeading = Optionen:%n +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_change-key-password.properties b/sop-java-picocli/src/main/resources/msg_change-key-password.properties index 3de3608..79bc11b 100644 --- a/sop-java-picocli/src/main/resources/msg_change-key-password.properties +++ b/sop-java-picocli/src/main/resources/msg_change-key-password.properties @@ -12,10 +12,15 @@ old-key-password.0=Old passwords to unlock the keys with. old-key-password.1=Multiple passwords can be passed in, which are tested sequentially to unlock locked subkeys. old-key-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +standardInput=KEYS +standardInputDescription=OpenPGP keys whose passphrases shall be changed +standardOutput=KEYS +standardOutputDescription=OpenPGP keys with changed passphrases + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nDescription:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties b/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties index 014c3e7..5515c1d 100644 --- a/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties +++ b/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties @@ -12,10 +12,13 @@ old-key-password.0=Alte Passw old-key-password.1=Mehrere Passwortkandidaten können übergeben werden, welche der Reihe nach durchprobiert werden, um Unterschlüssel zu entsperren. old-key-password.2=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +standardInputDescription=OpenPGP Schlüssel deren Passwörter geändert werden sollen +standardOutputDescription=OpenPGP Schlüssel mit geänderten Passwörtern + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nBeschreibung:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_dearmor.properties b/sop-java-picocli/src/main/resources/msg_dearmor.properties index b088de1..55cbf45 100644 --- a/sop-java-picocli/src/main/resources/msg_dearmor.properties +++ b/sop-java-picocli/src/main/resources/msg_dearmor.properties @@ -3,9 +3,14 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Remove ASCII Armor from standard input +standardInput=ARMORED +standardInputDescription=Armored OpenPGP material (SIGNATURES, KEYS, CERTS, CIPHERTEXT, INLINESIGNED) +standardOutput=BINARY +standardOutputDescription=Same material, but with ASCII-armoring removed + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_dearmor_de.properties b/sop-java-picocli/src/main/resources/msg_dearmor_de.properties index 362ccef..e01ab7a 100644 --- a/sop-java-picocli/src/main/resources/msg_dearmor_de.properties +++ b/sop-java-picocli/src/main/resources/msg_dearmor_de.properties @@ -3,9 +3,12 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Entferne ASCII Armor von Standard-Eingabe +standardInputDescription=OpenPGP Material mit ASCII Armor (SIGNATURES, KEYS, CERTS, CIPHERTEXT, INLINESIGNED) +standardOutputDescription=Dasselbe Material, aber mit entfernter ASCII Armor + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_decrypt.properties b/sop-java-picocli/src/main/resources/msg_decrypt.properties index 5903ded..bec315f 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt.properties @@ -22,10 +22,15 @@ with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). KEY[0..*]=Secret keys to attempt decryption with +standardInput=CIPHERTEXT +standardInputDescription=Encrypted OpenPGP message +standardOutput=DATA +standardOutputDescription=Decrypted OpenPGP message + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties index ba40897..395a89f 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties @@ -22,10 +22,13 @@ with-key-password.0=Passwort zum Entsperren der privaten Schl with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). KEY[0..*]=Private Schlüssel zum Entschlüsseln der Nachricht +standardInputDescription=Verschlüsselte OpenPGP Nachricht +standardOutputDescription=Entschlüsselte OpenPGP Nachricht + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign.properties b/sop-java-picocli/src/main/resources/msg_detached-sign.properties index 83359a6..6ebfd0b 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign.properties @@ -11,10 +11,15 @@ with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, f micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156). KEYS[0..*]=Secret keys used for signing +standardInput=DATA +standardInputDescription=Data that shall be signed +standardOutput=SIGNATURES +standardOutputDescription=Detached OpenPGP signature(s) + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties index b943da5..39b59b5 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties @@ -11,10 +11,13 @@ with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, micalg-out=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. KEYS[0..*]=Private Signaturschlüssel +standardInputDescription=Daten die signiert werden sollen +standardOutputDescription=Abgetrennte OpenPGP Signatur(en) + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify.properties b/sop-java-picocli/src/main/resources/msg_detached-verify.properties index ee1a468..074a318 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify.properties @@ -13,11 +13,16 @@ not-after.3=Accepts special value "-" for end of time. SIGNATURE[0]=Detached signature CERT[1..*]=Public key certificates for signature verification +standardInput=DATA +standardInputDescription=Data over which the detached signatures were calculated +standardOutput=VERIFICATIONS +standardOutputDescription=Information about successfully verified signatures + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nDescription:%n usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties index 332bff6..e21ee2a 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties @@ -13,11 +13,14 @@ not-after.3=Akzeptiert speziellen Wert '-' f SIGNATURE[0]=Abgetrennte Signatur CERT[1..*]=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung +standardInputDescription=Daten, über die die abgetrennten Signaturen erstellt wurden +standardOutputDescription=Informationen über erfolgreich verifizierte Signaturen + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nBeschreibung:%n usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_encrypt.properties b/sop-java-picocli/src/main/resources/msg_encrypt.properties index c0f7f7d..7bbf593 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt.properties @@ -12,10 +12,15 @@ with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). CERTS[0..*]=Certificates the message gets encrypted to +standardInput=DATA +standardInputDescription=Data that shall be encrypted +standardOutput=CIPHERTEXT +standardOutputDescription=Encrypted OpenPGP message + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties index 6a3055c..55b0338 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties @@ -12,10 +12,13 @@ with-key-password.0=Passwort zum Entsperren der privaten Schl with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). CERTS[0..*]=Zertifikate für die die Nachricht verschlüsselt werden soll +standardInputDescription=Daten, die verschlüsselt werden sollen +standardOutputDescription=Verschlüsselte OpenPGP Nachricht + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert.properties b/sop-java-picocli/src/main/resources/msg_extract-cert.properties index 82cac0f..1d1dee4 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert.properties @@ -5,10 +5,15 @@ usage.header=Extract a public key certificate from a secret key usage.description=Read a secret key from STDIN and emit the public key certificate to STDOUT. no-armor=ASCII armor the output +standardInput=KEYS +standardInputDescription=Private key(s), from which certificate(s) shall be extracted +standardOutput=CERTS +standardOutputDescription=Extracted certificate(s) + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nDescription:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties index 0946cfc..c92d31d 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties @@ -5,10 +5,13 @@ usage.header=Extrahiere Zertifikat ( usage.description=Lese einen Schlüssel von Standard-Eingabe und gebe das Zertifikat auf Standard-Ausgabe aus. no-armor=Schütze Ausgabe mit ASCII Armor +standardInputDescription=Private Schlüssel, deren Zertifikate extrahiert werden sollen +standardOutputDescription=Extrahierte Zertifikate + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nBeschreibung:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_generate-key.properties b/sop-java-picocli/src/main/resources/msg_generate-key.properties index 60ff4a4..c17f7f6 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key.properties @@ -9,10 +9,13 @@ signing-only=Generate a key that can only be used for signing with-key-password.0=Password to protect the private key with with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +standardOutput=KEYS +standardOutputDescription=Generated OpenPGP key + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties index 6a0ce13..84db04d 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties @@ -9,10 +9,12 @@ signing-only=Generiere einen Schl with-key-password.0=Passwort zum Schutz des privaten Schlüssels with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +standardOutputDescription=Erzeugter OpenPGP Schlüssel + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_help.properties b/sop-java-picocli/src/main/resources/msg_help.properties index 797cc79..637c1d0 100644 --- a/sop-java-picocli/src/main/resources/msg_help.properties +++ b/sop-java-picocli/src/main/resources/msg_help.properties @@ -6,6 +6,6 @@ usage.header=Display usage information for the specified subcommand stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_help_de.properties b/sop-java-picocli/src/main/resources/msg_help_de.properties index beea45c..8471188 100644 --- a/sop-java-picocli/src/main/resources/msg_help_de.properties +++ b/sop-java-picocli/src/main/resources/msg_help_de.properties @@ -7,5 +7,5 @@ stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-detach.properties b/sop-java-picocli/src/main/resources/msg_inline-detach.properties index c100c51..ca0ed6b 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-detach.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach.properties @@ -5,9 +5,14 @@ usage.header=Split signatures from a clearsigned message no-armor=ASCII armor the output signatures-out=Destination to which a detached signatures block will be written +standardInput=INLINESIGNED +standardInputDescription=Inline-signed OpenPGP message +standardOutput=DATA +standardOutputDescription=The message without any signatures + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties index e59aa34..84b8c47 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties @@ -5,9 +5,12 @@ usage.header=Trenne Signaturen von Klartext-signierter Nachricht no-armor=Schütze Ausgabe mit ASCII Armor signatures-out=Schreibe abgetrennte Signaturen in Ausgabe +standardInputDescription=Klartext-signierte OpenPGP Nachricht +standardOutputDescription=Nachricht ohne Signaturen + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign.properties b/sop-java-picocli/src/main/resources/msg_inline-sign.properties index f5143bb..936b417 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign.properties @@ -13,10 +13,15 @@ with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, f micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156). KEYS[0..*]=Secret keys used for signing +standardInput=DATA +standardInputDescription=Data that shall be signed +standardOutput=INLINESIGNED +standardOutputDescription=Inline-signed OpenPGP message + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties index b09b7e4..f8fe906 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties @@ -13,10 +13,13 @@ with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, micalg=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. KEYS[0..*]=Private Signaturschlüssel +standardInputDescription=Daten, die signiert werden sollen +standardOutputDescription=Inline-signierte OpenPGP Nachricht + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify.properties b/sop-java-picocli/src/main/resources/msg_inline-verify.properties index dfa94d7..2e0d69f 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify.properties @@ -12,10 +12,15 @@ not-after.3=Accepts special value "-" for end of time. verifications-out=File to write details over successful verifications to CERT[0..*]=Public key certificates for signature verification +standardInput=INLINESIGNED +standardInputDescription=Inline-signed OpenPGP message +standardOutput=DATA +standardOutputDescription=The message without any signatures + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties index a9a5722..9b70504 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties @@ -12,10 +12,13 @@ not-after.3=Akzeptiert speziellen Wert '-' f verifications-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe CERT[0..*]=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung +standardInputDescription=Inline-signierte OpenPGP Nachricht +standardOutputDescription=Nachricht ohne Signaturen + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_list-profiles.properties b/sop-java-picocli/src/main/resources/msg_list-profiles.properties index 6d5f1a8..3defe8e 100644 --- a/sop-java-picocli/src/main/resources/msg_list-profiles.properties +++ b/sop-java-picocli/src/main/resources/msg_list-profiles.properties @@ -4,10 +4,13 @@ usage.header=Emit a list of profiles supported by the identified subcommand subcommand=Subcommand for which to list profiles +standardOutput=PROFILELIST +standardOutputDescription=List of profiles supported by the identified subcommand + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties b/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties index ac03c0d..093aeb3 100644 --- a/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties +++ b/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties @@ -4,10 +4,12 @@ usage.header=Gebe eine Liste von Profilen aus, welche vom angegebenen Unterbefehl unterstützt werden subcommand=Unterbefehl, für welchen Profile gelistet werden sollen +standardOutputDescription=Liste von Profilen, die der identifizierte Unterbefehl unterstützt + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_merge-certs.properties b/sop-java-picocli/src/main/resources/msg_merge-certs.properties index 866db4b..b01f577 100644 --- a/sop-java-picocli/src/main/resources/msg_merge-certs.properties +++ b/sop-java-picocli/src/main/resources/msg_merge-certs.properties @@ -6,10 +6,16 @@ usage.description=BLABLA no-armor=ASCII armor the output CERTS[0..*]=OpenPGP certificates from which updates shall be merged into the base certificates from standard input +standardInput=CERTS +standardInputDescription=Base certificates into which additional elements from the command line shall be merged +standardOutput=CERTS +standardOutputDescription=Merged certificates + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.descriptionHeading=%nNote:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties b/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties index 021c535..b1f008c 100644 --- a/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties +++ b/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties @@ -1,19 +1,19 @@ # SPDX-FileCopyrightText: 2024 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.headerHeading=OpenPGP Zertifikate zusammenführen%n%n +usage.headerHeading=OpenPGP Zertifikate zusammenführen%n usage.header=Führe OpenPGP Zertifikate aus der Standardeingabe mit ensprechenden Elementen aus CERTS zusammen und gebe das Ergebnis auf der Standardausgabe aus usage.description=Es werden nur Zertifikate auf die Standardausgabe geschrieben, welche Teil der Standardeingabe waren no-armor=Schütze Ausgabe mit ASCII Armor CERTS[0..*]=OpenPGP Zertifikate aus denen neue Elemente in die Basiszertifikate aus der Standardeingabe übernommen werden sollen -usage.parameterList.0=STANDARDIN -usage.parameterList.1=STANDARDOUT +standardInputDescription=Basis-Zertifikate, in welche zusätzliche Elemente von der Kommandozeile zusammengeführt werden sollen +standardOutputDescription=Zusammengeführte Zertifikate # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 -usage.descriptionHeading=%nHinweise:%n +usage.descriptionHeading=%nHinweis:%n usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key.properties b/sop-java-picocli/src/main/resources/msg_revoke-key.properties index c7d72b3..f68b774 100644 --- a/sop-java-picocli/src/main/resources/msg_revoke-key.properties +++ b/sop-java-picocli/src/main/resources/msg_revoke-key.properties @@ -7,10 +7,15 @@ no-armor=ASCII armor the output with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +standardInput=KEYS +standardInputDescription=OpenPGP key that shall be revoked +standardOutput=CERTS +standardOutputDescription=Revocation certificate + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 -usage.descriptionHeading=%nDescription:%n +usage.descriptionHeading=D%nescription:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties index 95db272..fa8c5b4 100644 --- a/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties @@ -7,10 +7,13 @@ no-armor=Sch with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +standardInputDescription=OpenPGP Schlüssel, der widerrufen werden soll +standardOutputDescription=Widerrufszertifikat + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nBeschreibung:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index d5d997a..097a2e2 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -11,6 +11,7 @@ locale=Locale for description texts usage.synopsisHeading=Usage:\u0020 usage.commandListHeading=%nCommands:%n usage.optionListHeading=%nOptions:%n +usage.parameterListHeading=%nParameters:%n usage.footerHeading=Powered by picocli%n standardInputHeading=%nInput:%n diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 73efe89..99d28a7 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -11,6 +11,7 @@ locale=Gebietsschema f usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading=%nOptionen:%n +usage.parameterListHeading=%nParameter:%n usage.footerHeading=Powered by Picocli%n standardInputHeading=%nEingabe:%n diff --git a/sop-java-picocli/src/main/resources/msg_update-key.properties b/sop-java-picocli/src/main/resources/msg_update-key.properties index dd4446d..e12fbbc 100644 --- a/sop-java-picocli/src/main/resources/msg_update-key.properties +++ b/sop-java-picocli/src/main/resources/msg_update-key.properties @@ -10,10 +10,15 @@ with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, f merge-certs.0=Merge additional elements found in the corresponding CERTS objects into the updated secret keys merge-certs.1=This can be used, for example, to absorb a third-party certification into the Transferable Secret Key +standardInput=KEYS +standardInputDescription=OpenPGP key that shall be kept up-to-date +standardOutput=KEYS +standardOutputDescription=Updated OpenPGP key + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_update-key_de.properties b/sop-java-picocli/src/main/resources/msg_update-key_de.properties index 86b999e..1b8a84d 100644 --- a/sop-java-picocli/src/main/resources/msg_update-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_update-key_de.properties @@ -10,9 +10,12 @@ with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dat merge-certs.0=Führe zusätzliche Elemente aus entsprechenden CERTS Objekten mit dem privaten Schlüssel zusammen merge-certs.1=Dies kann zum Beispiel dazu genutzt werden, Zertifizierungen dritter in den privaten Schlüssel zu übernehmen +standardInputDescription=OpenPGP Schlüssel, der auf den neusten Stand gebracht werden soll +standardOutputDescription=Erneuerter OpenPGP Schlüssel + # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_version.properties b/sop-java-picocli/src/main/resources/msg_version.properties index 9e1451b..c7d0168 100644 --- a/sop-java-picocli/src/main/resources/msg_version.properties +++ b/sop-java-picocli/src/main/resources/msg_version.properties @@ -6,9 +6,11 @@ extended=Print an extended version string backend=Print information about the cryptographic backend sop-spec=Print the latest revision of the SOP specification targeted by the implementation +standardOutput=version information + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_version_de.properties b/sop-java-picocli/src/main/resources/msg_version_de.properties index 608b0c6..c317916 100644 --- a/sop-java-picocli/src/main/resources/msg_version_de.properties +++ b/sop-java-picocli/src/main/resources/msg_version_de.properties @@ -6,9 +6,11 @@ extended=Gebe erweiterte Versionsinformationen aus backend=Gebe Informationen über das kryptografische Backend aus sop-spec=Gebe die neuste Revision der SOP Spezifikation aus, welche von dieser Implementierung umgesetzt wird +standardOutput=Versionsinformationen + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n From e7f04584c87c74aa0b8a00d6f67a08558f730471 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 16:56:25 +0200 Subject: [PATCH 08/29] Implement validate-userid command --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 1 + .../cli/picocli/commands/ValidateUserIdCmd.kt | 74 ++++++++++++++++++ .../src/main/resources/msg_sop.properties | 2 + .../resources/msg_validate-userid.properties | 18 +++++ .../msg_validate-userid_de.properties | 18 +++++ sop-java/src/main/kotlin/sop/SOP.kt | 15 ++-- .../kotlin/sop/exception/SOPGPException.kt | 27 +++++-- .../kotlin/sop/operation/ValidateUserId.kt | 78 +++++++++++++++++++ 8 files changed, 216 insertions(+), 17 deletions(-) create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt create mode 100644 sop-java-picocli/src/main/resources/msg_validate-userid.properties create mode 100644 sop-java-picocli/src/main/resources/msg_validate-userid_de.properties create mode 100644 sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt 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 d234e54..dc14907 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 @@ -30,6 +30,7 @@ import sop.exception.SOPGPException UpdateKeyCmd::class, MergeCertsCmd::class, CertifyUserIdCmd::class, + ValidateUserIdCmd::class, // Messaging subcommands SignCmd::class, VerifyCmd::class, 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 new file mode 100644 index 0000000..c2de148 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +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 + +@Command( + name = "validate-userid", + resourceBundle = "msg_validate-userid", + exitCodeOnInvalidInput = SOPGPException.MissingArg.EXIT_CODE) +class ValidateUserIdCmd : AbstractSopCmd() { + + @Option(names = ["--addr-spec-only"]) var addrSpecOnly: Boolean = false + + @Parameters(index = "0", arity = "1", paramLabel = "USERID") lateinit var userId: String + + @Parameters(index = "1..*", arity = "1..*", paramLabel = "CERTS") + var authorities: List = listOf() + + override fun run() { + val validateUserId = + throwIfUnsupportedSubcommand(SopCLI.getSop().validateUserId(), "validate-userid") + + if (addrSpecOnly) { + validateUserId.addrSpecOnly() + } + + validateUserId.userId(userId) + + for (authority in authorities) { + try { + getInput(authority).use { validateUserId.authorities(it) } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (b: SOPGPException.BadData) { + val errorMsg = getMsg("sop.error.input.not_a_certificate", authority) + throw SOPGPException.BadData(errorMsg, b) + } + } + + try { + val valid = validateUserId.subjects(System.`in`) + + if (!valid) { + val errorMsg = getMsg("sop.error.runtime.any_cert_user_id_no_match", userId) + throw SOPGPException.CertUserIdNoMatch(errorMsg) + } + } catch (e: SOPGPException.CertUserIdNoMatch) { + val errorMsg = + if (e.fingerprint != null) { + getMsg( + "sop.error.runtime.cert_user_id_no_match", + bytesToHex(e.fingerprint!!), + userId) + } else { + getMsg("sop.error.runtime.any_cert_user_id_no_match", userId) + } + throw SOPGPException.CertUserIdNoMatch(errorMsg, e) + } catch (e: SOPGPException.BadData) { + val errorMsg = getMsg("sop.error.input.not_a_certificate", "STDIN") + throw SOPGPException.BadData(errorMsg, e) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 097a2e2..520533a 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -80,6 +80,8 @@ sop.error.runtime.cert_cannot_encrypt=Certificate from input '%s' cannot encrypt sop.error.runtime.no_session_key_extracted=Session key not extracted. Feature potentially not supported. sop.error.runtime.no_verifiable_signature_found=No verifiable signature found. sop.error.runtime.cannot_decrypt_message=Message could not be decrypted. +sop.error.runtime.cert_user_id_no_match=Certificate '%s' does not contain a valid binding for user id '%s'. +sop.error.runtime.any_cert_user_id_no_match=Any certificate does not contain a valid binding for user id '%s'. ## Usage errors sop.error.usage.password_or_cert_required=At least one password file or cert file required for encryption. sop.error.usage.argument_required=Argument '%s' is required. diff --git a/sop-java-picocli/src/main/resources/msg_validate-userid.properties b/sop-java-picocli/src/main/resources/msg_validate-userid.properties new file mode 100644 index 0000000..5cfed2d --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_validate-userid.properties @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Validate a UserID in an OpenPGP certificate +addr-spec-only=Treat the USERID as an email address, match only against the email address part of each correctly bound UserID +USERID[0]=UserID +CERTS[1..*]=Authority OpenPGP certificates + +standardInput=CERTS +standardInputDescription=OpenPGP certificates in which UserID bindings shall be validated + +stacktrace=Print stacktrace +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties b/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties new file mode 100644 index 0000000..8231c6a --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Validiere eine UserID auf OpenPGP Zertifikaten +addr-spec-only=Behandle die USERID als E-Mail-Adresse, vergleiche sie nur mit dem E-Mail-Adressen-Teil jeder korrekten UserID +USERID[0]=UserID +CERTS[1..*]=Autoritäre OpenPGP Zertifikate + +standardInput=CERTS +standardInputDescription=OpenPGP Zertifikate auf denen UserIDs validiert werden sollen + +stacktrace=Print stacktrace +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading=%nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index 5435cad..c5f05e2 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -60,18 +60,15 @@ interface SOP : SOPV { /** Update a key's password. */ fun changeKeyPassword(): ChangeKeyPassword - /** - * Keep a secret key up-to-date. - */ + /** Keep a secret key up-to-date. */ fun updateKey(): UpdateKey - /** - * Merge OpenPGP certificates. - */ + /** Merge OpenPGP certificates. */ fun mergeCerts(): MergeCerts - /** - * Certify OpenPGP Certificate User-IDs. - */ + /** Certify OpenPGP Certificate User-IDs. */ fun certifyUserId(): CertifyUserId + + /** Validate a UserID in an OpenPGP certificate. */ + fun validateUserId(): ValidateUserId } diff --git a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt index 1f9ce6b..862e1bd 100644 --- a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt +++ b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt @@ -338,9 +338,7 @@ abstract class SOPGPException : RuntimeException { } } - /** - * The primary key of a KEYS object is too weak or revoked. - */ + /** The primary key of a KEYS object is too weak or revoked. */ class PrimaryKeyBad : SOPGPException { constructor() : super() @@ -353,13 +351,26 @@ abstract class SOPGPException : RuntimeException { } } - /** - * The CERTS object has no matching User ID. - */ + /** The CERTS object has no matching User ID. */ class CertUserIdNoMatch : SOPGPException { - constructor() : super() - constructor(errorMsg: String) : super(errorMsg) + val fingerprint: ByteArray? + + constructor() : super() { + fingerprint = null + } + + constructor(fingerprint: ByteArray) : super() { + this.fingerprint = fingerprint + } + + constructor(errorMsg: String) : super(errorMsg) { + fingerprint = null + } + + constructor(errorMsg: String, cause: Throwable) : super(errorMsg, cause) { + fingerprint = null + } override fun getExitCode(): Int = EXIT_CODE diff --git a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt new file mode 100644 index 0000000..4f4c51a --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.exception.SOPGPException + +/** Subcommand to validate UserIDs on certificates. */ +interface ValidateUserId { + + /** + * If this is set, then the USERID is treated as an e-mail address, and matched only against the + * e-mail address part of each correctly bound User ID. The rest of each correctly bound User ID + * is ignored. + * + * @return this + */ + @Throws(SOPGPException.UnsupportedOption::class) fun addrSpecOnly(): ValidateUserId + + /** + * Set the UserID to validate. To match only the email address, call [addrSpecOnly]. + * + * @param userId UserID or email address + * @return this + */ + fun userId(userId: String): ValidateUserId + + /** + * Add certificates, which act as authorities. The [userId] is only considered correctly bound, + * if it was bound by an authoritative certificate. + * + * @param certs authoritative certificates + * @return this + */ + @Throws(SOPGPException.BadData::class, IOException::class) + fun authorities(certs: InputStream): ValidateUserId + + /** + * Add certificates, which act as authorities. The [userId] is only considered correctly bound, + * if it was bound by an authoritative certificate. + * + * @param certs authoritative certificates + * @return this + */ + @Throws(SOPGPException.BadData::class, IOException::class) + fun authorities(certs: ByteArray): ValidateUserId = authorities(certs.inputStream()) + + /** + * Add subject certificates, on which UserID bindings are validated. + * + * @param certs subject certificates + * @return true if all subject certificates have a correct binding to the UserID. + * @throws SOPGPException.BadData if the subject certificates are malformed + * @throws IOException if a parser exception happens + * @throws SOPGPException.CertUserIdNoMatch if any subject certificate does not have a correctly + * bound UserID that matches [userId]. + */ + @Throws( + SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + fun subjects(certs: InputStream): Boolean + + /** + * Add subject certificates, on which UserID bindings are validated. + * + * @param certs subject certificates + * @return true if all subject certificates have a correct binding to the UserID. + * @throws SOPGPException.BadData if the subject certificates are malformed + * @throws IOException if a parser exception happens + * @throws SOPGPException.CertUserIdNoMatch if any subject certificate does not have a correctly + * bound UserID that matches [userId]. + */ + @Throws( + SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + fun subjects(certs: ByteArray): Boolean = subjects(certs.inputStream()) +} From e9c2bc8a3b0df8fd0dfd476c7e3974784b88e5c5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 16:56:43 +0200 Subject: [PATCH 09/29] Checkstyle --- .../kotlin/sop/operation/CertifyUserId.kt | 24 ++++++++--------- .../main/kotlin/sop/operation/MergeCerts.kt | 9 +++---- .../main/kotlin/sop/operation/UpdateKey.kt | 27 +++++++++++++------ 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt b/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt index 92fff20..642966b 100644 --- a/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt +++ b/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt @@ -4,28 +4,26 @@ package sop.operation +import java.io.IOException +import java.io.InputStream import sop.Ready import sop.exception.SOPGPException import sop.util.UTF8Util -import java.io.IOException -import java.io.InputStream interface CertifyUserId { - @Throws(SOPGPException.UnsupportedOption::class) - fun noArmor(): CertifyUserId + @Throws(SOPGPException.UnsupportedOption::class) fun noArmor(): CertifyUserId - @Throws(SOPGPException.UnsupportedOption::class) - fun userId(userId: String): CertifyUserId + @Throws(SOPGPException.UnsupportedOption::class) fun userId(userId: String): CertifyUserId @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) - fun withKeyPassword(password: String): CertifyUserId = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + fun withKeyPassword(password: String): CertifyUserId = + withKeyPassword(password.toByteArray(UTF8Util.UTF8)) @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) fun withKeyPassword(password: ByteArray): CertifyUserId - @Throws(SOPGPException.UnsupportedOption::class) - fun noRequireSelfSig(): CertifyUserId + @Throws(SOPGPException.UnsupportedOption::class) fun noRequireSelfSig(): CertifyUserId @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class) fun keys(keys: InputStream): CertifyUserId @@ -33,9 +31,11 @@ interface CertifyUserId { @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class) fun keys(keys: ByteArray): CertifyUserId = keys(keys.inputStream()) - @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + @Throws( + SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) fun certs(certs: InputStream): Ready - @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + @Throws( + SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) fun certs(certs: ByteArray): Ready = certs(certs.inputStream()) -} \ No newline at end of file +} diff --git a/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt b/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt index f60d291..f922490 100644 --- a/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt +++ b/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt @@ -4,15 +4,14 @@ package sop.operation -import sop.Ready -import sop.exception.SOPGPException import java.io.IOException import java.io.InputStream +import sop.Ready +import sop.exception.SOPGPException interface MergeCerts { - @Throws(SOPGPException.UnsupportedOption::class) - fun noArmor(): MergeCerts + @Throws(SOPGPException.UnsupportedOption::class) fun noArmor(): MergeCerts @Throws(SOPGPException.BadData::class, IOException::class) fun updates(updateCerts: InputStream): MergeCerts @@ -25,4 +24,4 @@ interface MergeCerts { @Throws(SOPGPException.BadData::class, IOException::class) fun baseCertificates(certs: ByteArray): Ready = baseCertificates(certs.inputStream()) -} \ No newline at end of file +} diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt index 1b12f6f..6c32b22 100644 --- a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -4,11 +4,11 @@ package sop.operation +import java.io.IOException +import java.io.InputStream import sop.Ready import sop.exception.SOPGPException import sop.util.UTF8Util -import java.io.IOException -import java.io.InputStream interface UpdateKey { @@ -24,20 +24,31 @@ interface UpdateKey { @Throws(SOPGPException.UnsupportedOption::class) fun noNewMechanisms(): UpdateKey @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) - fun withKeyPassword(password: String): UpdateKey = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + fun withKeyPassword(password: String): UpdateKey = + withKeyPassword(password.toByteArray(UTF8Util.UTF8)) @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) fun withKeyPassword(password: ByteArray): UpdateKey - @Throws(SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) + @Throws( + SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) fun mergeCerts(certs: InputStream): UpdateKey - @Throws(SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) + @Throws( + SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) fun mergeCerts(certs: ByteArray): UpdateKey = mergeCerts(certs.inputStream()) - @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class, SOPGPException.PrimaryKeyBad::class) + @Throws( + SOPGPException.BadData::class, + IOException::class, + SOPGPException.KeyIsProtected::class, + SOPGPException.PrimaryKeyBad::class) fun key(key: InputStream): Ready - @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class, SOPGPException.PrimaryKeyBad::class) + @Throws( + SOPGPException.BadData::class, + IOException::class, + SOPGPException.KeyIsProtected::class, + SOPGPException.PrimaryKeyBad::class) fun key(key: ByteArray): Ready = key(key.inputStream()) -} \ No newline at end of file +} From a8b51d44b9a475516d90d6aaebfb0d0590a65ce7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 17:51:04 +0200 Subject: [PATCH 10/29] Implement external variants of new subcommands --- .../main/kotlin/sop/external/ExternalSOP.kt | 20 ++++++++ .../operation/CertifyUserIdExternal.kt | 48 +++++++++++++++++++ .../external/operation/MergeCertsExternal.kt | 30 ++++++++++++ .../external/operation/UpdateKeyExternal.kt | 41 ++++++++++++++++ .../operation/ValidateUserIdExternal.kt | 38 +++++++++++++++ 5 files changed, 177 insertions(+) create mode 100644 external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt create mode 100644 external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt create mode 100644 external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt create mode 100644 external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt diff --git a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt index 27c93ae..8ab7737 100644 --- a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt +++ b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt @@ -69,6 +69,14 @@ class ExternalSOP( override fun changeKeyPassword(): ChangeKeyPassword = ChangeKeyPasswordExternal(binaryName, properties) + override fun updateKey(): UpdateKey = UpdateKeyExternal(binaryName, properties) + + override fun mergeCerts(): MergeCerts = MergeCertsExternal(binaryName, properties) + + override fun certifyUserId(): CertifyUserId = CertifyUserIdExternal(binaryName, properties) + + override fun validateUserId(): ValidateUserId = ValidateUserIdExternal(binaryName, properties) + /** * This interface can be used to provide a directory in which external SOP binaries can * temporarily store additional results of OpenPGP operations such that the binding classes can @@ -169,6 +177,18 @@ class ExternalSOP( UnsupportedProfile.EXIT_CODE -> throw UnsupportedProfile( "External SOP backend reported error UnsupportedProfile ($exitCode):\n$errorMessage") + NoHardwareKeyFound.EXIT_CODE -> + throw NoHardwareKeyFound( + "External SOP backend reported error NoHardwareKeyFound ($exitCode):\n$errorMessage") + HardwareKeyFailure.EXIT_CODE -> + throw HardwareKeyFailure( + "External SOP backend reported error HardwareKeyFalure ($exitCode):\n$errorMessage") + PrimaryKeyBad.EXIT_CODE -> + throw PrimaryKeyBad( + "External SOP backend reported error PrimaryKeyBad ($exitCode):\n$errorMessage") + CertUserIdNoMatch.EXIT_CODE -> + throw CertUserIdNoMatch( + "External SOP backend reported error CertUserIdNoMatch ($exitCode):\n$errorMessage") // Did you forget to add a case for a new exception type? else -> diff --git a/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt new file mode 100644 index 0000000..abf4d50 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.* +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.CertifyUserId + +class CertifyUserIdExternal(binary: String, environment: Properties) : CertifyUserId { + + private val commandList = mutableListOf(binary, "version") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCount = 0 + + private val keys: MutableList = mutableListOf() + + override fun noArmor(): CertifyUserId = apply { commandList.add("--no-armor") } + + override fun userId(userId: String): CertifyUserId = apply { + commandList.add("--userid") + commandList.add(userId) + } + + override fun withKeyPassword(password: ByteArray): CertifyUserId = apply { + commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCount") + envList.add("KEY_PASSWORD_$argCount=${String(password)}") + argCount += 1 + } + + override fun noRequireSelfSig(): CertifyUserId = apply { + commandList.add("--no-require-self-sig") + } + + override fun keys(keys: InputStream): CertifyUserId = apply { + this.keys.add("@ENV:KEY_$argCount") + envList.add("KEY_$argCount=${ExternalSOP.readString(keys)}") + argCount += 1 + } + + override fun certs(certs: InputStream): Ready = + ExternalSOP.executeTransformingOperation( + Runtime.getRuntime(), commandList.plus(keys), envList, certs) +} diff --git a/external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt new file mode 100644 index 0000000..0869fab --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.* +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.MergeCerts + +class MergeCertsExternal(binary: String, environment: Properties) : MergeCerts { + + private val commandList = mutableListOf(binary, "version") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCount = 0 + + override fun noArmor(): MergeCerts = apply { commandList.add("--no-armor") } + + override fun updates(updateCerts: InputStream): MergeCerts = apply { + commandList.add("@ENV:CERT_$argCount") + envList.add("CERT_$argCount=${ExternalSOP.readString(updateCerts)}") + argCount += 1 + } + + override fun baseCertificates(certs: InputStream): Ready = + ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, certs) +} diff --git a/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt new file mode 100644 index 0000000..9aa1d29 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.* +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.UpdateKey + +class UpdateKeyExternal(binary: String, environment: Properties) : UpdateKey { + + private val commandList = mutableListOf(binary, "update-key") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCount = 0 + + override fun noArmor(): UpdateKey = apply { commandList.add("--no-armor") } + + override fun signingOnly(): UpdateKey = apply { commandList.add("--signing-only") } + + 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") + envList.add("KEY_PASSWORD_$argCount=${String(password)}") + argCount += 1 + } + + override fun mergeCerts(certs: InputStream): UpdateKey = apply { + commandList.add("--merge-certs") + commandList.add("@ENV:CERT_$argCount") + envList.add("CERT_$argCount=${ExternalSOP.readString(certs)}") + argCount += 1 + } + + override fun key(key: InputStream): Ready = + ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, key) +} diff --git a/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt new file mode 100644 index 0000000..867a755 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.* +import sop.external.ExternalSOP +import sop.operation.ValidateUserId + +class ValidateUserIdExternal(binary: String, environment: Properties) : ValidateUserId { + + private val commandList = mutableListOf(binary, "version") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCount = 0 + + private var userId: String? = null + private val authorities: MutableList = mutableListOf() + + override fun addrSpecOnly(): ValidateUserId = apply { commandList.add("--addr-spec-only") } + + override fun userId(userId: String): ValidateUserId = apply { this.userId = userId } + + override fun authorities(certs: InputStream): ValidateUserId = apply { + this.authorities.add("@ENV:CERT_$argCount") + envList.add("CERT_$argCount=${ExternalSOP.readString(certs)}") + argCount += 1 + } + + override fun subjects(certs: InputStream): Boolean { + ExternalSOP.executeTransformingOperation( + Runtime.getRuntime(), commandList.plus(userId!!).plus(authorities), envList, certs) + .bytes + return true + } +} From dec1908d599e68611d37143432de6a298e8c62fe Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 18:25:03 +0200 Subject: [PATCH 11/29] Show endOfOptions delimiter in help --- .../kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt | 7 ++----- .../kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt index 71ef79f..228809b 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt @@ -6,10 +6,8 @@ package sop.cli.picocli.commands import java.io.IOException import picocli.CommandLine.Command -import picocli.CommandLine.Model.CommandSpec import picocli.CommandLine.Option import picocli.CommandLine.Parameters -import picocli.CommandLine.Spec import sop.cli.picocli.SopCLI import sop.exception.SOPGPException.BadData import sop.exception.SOPGPException.UnsupportedOption @@ -17,11 +15,10 @@ import sop.exception.SOPGPException.UnsupportedOption @Command( name = "certify-userid", resourceBundle = "msg_certify-userid", - exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE, + showEndOfOptionsDelimiterInUsageHelp = true) class CertifyUserIdCmd : AbstractSopCmd() { - @Spec var spec: CommandSpec? = null - @Option(names = ["--no-armor"], negatable = true) var armor = true @Option(names = ["--userid"], required = true, arity = "1..*", paramLabel = "USERID") 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 c2de148..da81a27 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 @@ -15,7 +15,8 @@ import sop.util.HexUtil.Companion.bytesToHex @Command( name = "validate-userid", resourceBundle = "msg_validate-userid", - exitCodeOnInvalidInput = SOPGPException.MissingArg.EXIT_CODE) + exitCodeOnInvalidInput = SOPGPException.MissingArg.EXIT_CODE, + showEndOfOptionsDelimiterInUsageHelp = true) class ValidateUserIdCmd : AbstractSopCmd() { @Option(names = ["--addr-spec-only"]) var addrSpecOnly: Boolean = false From d95e28af00c4fdce74e9e67eda7470ab3337fc08 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 18:25:17 +0200 Subject: [PATCH 12/29] Add first test for new commands --- .../test/java/sop/cli/picocli/SOPTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index fe49472..4d36322 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -13,10 +13,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import sop.SOP; import sop.exception.SOPGPException; import sop.operation.Armor; +import sop.operation.CertifyUserId; import sop.operation.ChangeKeyPassword; import sop.operation.Dearmor; import sop.operation.Decrypt; @@ -29,7 +31,10 @@ import sop.operation.InlineVerify; import sop.operation.DetachedSign; import sop.operation.DetachedVerify; import sop.operation.ListProfiles; +import sop.operation.MergeCerts; import sop.operation.RevokeKey; +import sop.operation.UpdateKey; +import sop.operation.ValidateUserId; import sop.operation.Version; public class SOPTest { @@ -52,6 +57,30 @@ public class SOPTest { @Test public void UnsupportedSubcommandsTest() { SOP nullCommandSOP = new SOP() { + @NotNull + @Override + public ValidateUserId validateUserId() { + return null; + } + + @NotNull + @Override + public CertifyUserId certifyUserId() { + return null; + } + + @NotNull + @Override + public MergeCerts mergeCerts() { + return null; + } + + @NotNull + @Override + public UpdateKey updateKey() { + return null; + } + @Override public Version version() { return null; @@ -140,6 +169,11 @@ public class SOPTest { commands.add(new String[] {"sign"}); commands.add(new String[] {"verify", "signature.asc", "cert.asc"}); commands.add(new String[] {"version"}); + commands.add(new String[] {"list-profiles", "generate-key"}); + commands.add(new String[] {"certify-userid", "--userid", "Alice ", "--", "alice.pgp"}); + commands.add(new String[] {"validate-userid", "Alice ", "bob.pgp", "--", "alice.pgp"}); + commands.add(new String[] {"update-key"}); + commands.add(new String[] {"merge-certs"}); for (String[] command : commands) { int exit = SopCLI.execute(command); From bfe2e5f707c5280e88d330ace62f433b39cb8d14 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 18:40:55 +0200 Subject: [PATCH 13/29] Fix nullability of sop commands --- .../test/java/sop/cli/picocli/SOPTest.java | 5 --- sop-java/src/main/kotlin/sop/SOP.kt | 34 +++++++++---------- sop-java/src/main/kotlin/sop/SOPV.kt | 8 ++--- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index 4d36322..62c7581 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import sop.SOP; import sop.exception.SOPGPException; @@ -57,25 +56,21 @@ public class SOPTest { @Test public void UnsupportedSubcommandsTest() { SOP nullCommandSOP = new SOP() { - @NotNull @Override public ValidateUserId validateUserId() { return null; } - @NotNull @Override public CertifyUserId certifyUserId() { return null; } - @NotNull @Override public MergeCerts mergeCerts() { return null; } - @NotNull @Override public UpdateKey updateKey() { return null; diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index c5f05e2..fbd0428 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -15,60 +15,60 @@ import sop.operation.* interface SOP : SOPV { /** Generate a secret key. */ - fun generateKey(): GenerateKey + fun generateKey(): GenerateKey? /** Extract a certificate (public key) from a secret key. */ - fun extractCert(): ExtractCert + fun extractCert(): ExtractCert? /** * Create detached signatures. If you want to sign a message inline, use [inlineSign] instead. */ - fun sign(): DetachedSign = detachedSign() + fun sign(): DetachedSign? = detachedSign() /** * Create detached signatures. If you want to sign a message inline, use [inlineSign] instead. */ - fun detachedSign(): DetachedSign + fun detachedSign(): DetachedSign? /** * Sign a message using inline signatures. If you need to create detached signatures, use * [detachedSign] instead. */ - fun inlineSign(): InlineSign + fun inlineSign(): InlineSign? /** Detach signatures from an inline signed message. */ - fun inlineDetach(): InlineDetach + fun inlineDetach(): InlineDetach? /** Encrypt a message. */ - fun encrypt(): Encrypt + fun encrypt(): Encrypt? /** Decrypt a message. */ - fun decrypt(): Decrypt + fun decrypt(): Decrypt? /** Convert binary OpenPGP data to ASCII. */ - fun armor(): Armor + fun armor(): Armor? /** Converts ASCII armored OpenPGP data to binary. */ - fun dearmor(): Dearmor + fun dearmor(): Dearmor? /** List supported [Profiles][Profile] of a subcommand. */ - fun listProfiles(): ListProfiles + fun listProfiles(): ListProfiles? /** Revoke one or more secret keys. */ - fun revokeKey(): RevokeKey + fun revokeKey(): RevokeKey? /** Update a key's password. */ - fun changeKeyPassword(): ChangeKeyPassword + fun changeKeyPassword(): ChangeKeyPassword? /** Keep a secret key up-to-date. */ - fun updateKey(): UpdateKey + fun updateKey(): UpdateKey? /** Merge OpenPGP certificates. */ - fun mergeCerts(): MergeCerts + fun mergeCerts(): MergeCerts? /** Certify OpenPGP Certificate User-IDs. */ - fun certifyUserId(): CertifyUserId + fun certifyUserId(): CertifyUserId? /** Validate a UserID in an OpenPGP certificate. */ - fun validateUserId(): ValidateUserId + fun validateUserId(): ValidateUserId? } diff --git a/sop-java/src/main/kotlin/sop/SOPV.kt b/sop-java/src/main/kotlin/sop/SOPV.kt index d331559..58a7f13 100644 --- a/sop-java/src/main/kotlin/sop/SOPV.kt +++ b/sop-java/src/main/kotlin/sop/SOPV.kt @@ -12,23 +12,23 @@ import sop.operation.Version interface SOPV { /** Get information about the implementations name and version. */ - fun version(): Version + fun version(): Version? /** * Verify detached signatures. If you need to verify an inline-signed message, use * [inlineVerify] instead. */ - fun verify(): DetachedVerify = detachedVerify() + fun verify(): DetachedVerify? = detachedVerify() /** * Verify detached signatures. If you need to verify an inline-signed message, use * [inlineVerify] instead. */ - fun detachedVerify(): DetachedVerify + 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 + fun inlineVerify(): InlineVerify? } From c311d4106f3e90f321f24b409aa470c872bccd37 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 19:19:56 +0200 Subject: [PATCH 14/29] Document endOfOptionsDelimiter --- .../src/main/resources/msg_certify-userid.properties | 2 ++ .../src/main/resources/msg_certify-userid_de.properties | 2 ++ .../src/main/resources/msg_validate-userid.properties | 2 ++ .../src/main/resources/msg_validate-userid_de.properties | 2 ++ 4 files changed, 8 insertions(+) diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid.properties b/sop-java-picocli/src/main/resources/msg_certify-userid.properties index 252aae4..36dc6f4 100644 --- a/sop-java-picocli/src/main/resources/msg_certify-userid.properties +++ b/sop-java-picocli/src/main/resources/msg_certify-userid.properties @@ -14,6 +14,8 @@ standardInputDescription=Certificates that shall be certified standardOutput=CERTS standardOutputDescription=Certified certificates +picocli.endofoptions.description=End of options. Remainder are positional parameters. Fixes 'Missing required parameter' error + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties index 9f0a673..d634c59 100644 --- a/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties +++ b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties @@ -12,6 +12,8 @@ KEYS[0..*]=Private Schl standardInputDescription=Zertifikate, auf denen Identitäten zertifiziert werden sollen standardOutputDescription=Zertifizierte Zertifikate +picocli.endofoptions.description=Ende der Optionen. Der Rest sind Positionsparameter. Behebt 'Missing required parameter' Fehler + # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 diff --git a/sop-java-picocli/src/main/resources/msg_validate-userid.properties b/sop-java-picocli/src/main/resources/msg_validate-userid.properties index 5cfed2d..d25fa3a 100644 --- a/sop-java-picocli/src/main/resources/msg_validate-userid.properties +++ b/sop-java-picocli/src/main/resources/msg_validate-userid.properties @@ -9,6 +9,8 @@ CERTS[1..*]=Authority OpenPGP certificates standardInput=CERTS standardInputDescription=OpenPGP certificates in which UserID bindings shall be validated +picocli.endofoptions.description=End of options. Remainder are positional parameters. Fixes 'Missing required parameter' error + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n diff --git a/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties b/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties index 8231c6a..f919465 100644 --- a/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties +++ b/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties @@ -9,6 +9,8 @@ CERTS[1..*]=Autorit standardInput=CERTS standardInputDescription=OpenPGP Zertifikate auf denen UserIDs validiert werden sollen +picocli.endofoptions.description=Ende der Optionen. Der Rest sind Positionsparameter. Behebt 'Missing required parameter' Fehler + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n From 68265671a5e07b9e9d8477c085bf734c8191c706 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 20:26:36 +0200 Subject: [PATCH 15/29] Bump version --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 9619f9a..c757e1e 100644 --- a/version.gradle +++ b/version.gradle @@ -4,7 +4,7 @@ allprojects { ext { - shortVersion = '10.1.1' + shortVersion = '11.0.0' isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 11 From 6eb8883563356e0cbc3e1e00b7616a8bb2c95b4e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 20:49:00 +0200 Subject: [PATCH 16/29] Fix documentation of merge-certs command --- sop-java-picocli/src/main/resources/msg_merge-certs.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/resources/msg_merge-certs.properties b/sop-java-picocli/src/main/resources/msg_merge-certs.properties index b01f577..8c0bfa3 100644 --- a/sop-java-picocli/src/main/resources/msg_merge-certs.properties +++ b/sop-java-picocli/src/main/resources/msg_merge-certs.properties @@ -2,7 +2,8 @@ # # SPDX-License-Identifier: Apache-2.0 usage.headerHeading=Merge OpenPGP certificates%n -usage.description=BLABLA +usage.header=Merge OpenPGP certificates from standard input with related elements from CERTS and emit the result to standard output +usage.description=Only certificates that were part of standard input will be emitted to standard output no-armor=ASCII armor the output CERTS[0..*]=OpenPGP certificates from which updates shall be merged into the base certificates from standard input From 227081f1ebeb4fdb28a22e9a63fabd1abf891da0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 8 Apr 2025 13:13:15 +0200 Subject: [PATCH 17/29] Remove call to explicitly set bundle to fix native image --- sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt | 2 -- 1 file changed, 2 deletions(-) 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 dc14907..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 @@ -85,8 +85,6 @@ class SopCLI { return CommandLine(SopCLI::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) // render Input/Output sections in help command From dd377619a19654a319a14dca6c317239f6e48096 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 8 Apr 2025 17:08:06 +0200 Subject: [PATCH 18/29] validate-userid: Add --validate-at option --- .../sop/external/operation/ValidateUserIdExternal.kt | 5 +++++ .../kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt | 7 +++++++ sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt | 3 +++ 3 files changed, 15 insertions(+) diff --git a/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt index 867a755..581d6f5 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt @@ -8,6 +8,7 @@ import java.io.InputStream import java.util.* import sop.external.ExternalSOP import sop.operation.ValidateUserId +import sop.util.UTCUtil class ValidateUserIdExternal(binary: String, environment: Properties) : ValidateUserId { @@ -35,4 +36,8 @@ class ValidateUserIdExternal(binary: String, environment: Properties) : Validate .bytes return true } + + override fun validateAt(date: Date): ValidateUserId = apply { + commandList.add("--validate-at=${UTCUtil.formatUTCDate(date)}") + } } 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 da81a27..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 @@ -11,6 +11,7 @@ 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", @@ -21,6 +22,8 @@ class ValidateUserIdCmd : AbstractSopCmd() { @Option(names = ["--addr-spec-only"]) var addrSpecOnly: Boolean = false + @Option(names = ["--validate-at"]) var validateAt: Date? = null + @Parameters(index = "0", arity = "1", paramLabel = "USERID") lateinit var userId: String @Parameters(index = "1..*", arity = "1..*", paramLabel = "CERTS") @@ -34,6 +37,10 @@ class ValidateUserIdCmd : AbstractSopCmd() { validateUserId.addrSpecOnly() } + if (validateAt != null) { + validateUserId.validateAt(validateAt!!) + } + validateUserId.userId(userId) for (authority in authorities) { diff --git a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt index 4f4c51a..fb8cab6 100644 --- a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt +++ b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt @@ -7,6 +7,7 @@ package sop.operation import java.io.IOException import java.io.InputStream import sop.exception.SOPGPException +import java.util.* /** Subcommand to validate UserIDs on certificates. */ interface ValidateUserId { @@ -75,4 +76,6 @@ interface ValidateUserId { @Throws( SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) fun subjects(certs: ByteArray): Boolean = subjects(certs.inputStream()) + + fun validateAt(date: Date): ValidateUserId } From 7a04783f12471c202310dba948cd8e2c29ff3233 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 12:26:29 +0200 Subject: [PATCH 19/29] MergeCertsCmd: Fix default value of armor --- .../src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 16d56e3..3dcef38 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 = false + @CommandLine.Option(names = ["--no-armor"], negatable = true) var armor = true @CommandLine.Parameters(paramLabel = "CERTS") var updates: List = listOf() From 9583c03cd1de850b57d9837de43f585dd880971e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 12:46:50 +0200 Subject: [PATCH 20/29] Fix formatting --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 8 ++++++-- .../kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt | 2 +- sop-java/src/main/kotlin/sop/SigningResult.kt | 7 ++++--- sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) 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 943f0f3..98701fc 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,7 +88,10 @@ 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 @@ -96,7 +99,8 @@ 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/ValidateUserIdCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt index 9a09a15..b83e5a8 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/src/main/kotlin/sop/SigningResult.kt b/sop-java/src/main/kotlin/sop/SigningResult.kt index 60888e0..651f8c1 100644 --- a/sop-java/src/main/kotlin/sop/SigningResult.kt +++ b/sop-java/src/main/kotlin/sop/SigningResult.kt @@ -9,9 +9,10 @@ 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/operation/ValidateUserId.kt b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt index fb8cab6..fe20fd4 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 sop.exception.SOPGPException import java.util.* +import sop.exception.SOPGPException /** Subcommand to validate UserIDs on certificates. */ interface ValidateUserId { From 86ff389388809c4cf18d8e86f358489a97b08b90 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 12:47:00 +0200 Subject: [PATCH 21/29] Add new Exception types --- .../kotlin/sop/exception/SOPGPException.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt index 862e1bd..9df1628 100644 --- a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt +++ b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt @@ -16,6 +16,22 @@ 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 @@ -378,4 +394,23 @@ 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 + } + } } From 6a1df7a1923235e0bba80f92aa7c6e7ed015c04b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 13:50:35 +0200 Subject: [PATCH 22/29] reuse: convert dep5 file to toml file --- .reuse/dep5 | 29 ----------------------------- REUSE.toml | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 29 deletions(-) delete mode 100644 .reuse/dep5 create mode 100644 REUSE.toml diff --git a/.reuse/dep5 b/.reuse/dep5 deleted file mode 100644 index f43556c..0000000 --- a/.reuse/dep5 +++ /dev/null @@ -1,29 +0,0 @@ -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 new file mode 100644 index 0000000..7e1b250 --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,32 @@ +# 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" From dd8526a0bcec8de951e414fe13073218b276f6e9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 13:50:57 +0200 Subject: [PATCH 23/29] Add basic test for certify-userid and validate-userid subcommands --- .../operation/CertifyValidateUserIdTest.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java 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 new file mode 100644 index 0000000..eabda44 --- /dev/null +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java @@ -0,0 +1,111 @@ +// 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 has not yet certified Bobs user-id + assertFalse(sop.validateUserId() + .authorities(aliceCert) + .userId("Bob ") + .subjects(bobCert)); + + byte[] bobCertifiedByAlice = sop.certifyUserId() + .userId("Bob ") + .withKeyPassword("sw0rdf1sh") + .keys(aliceKey) + .certs(bobCert) + .getBytes(); + + assertTrue(sop.validateUserId() + .userId("Bob ") + .authorities(aliceCert) + .subjects(bobCertifiedByAlice)); + } + + @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()); + + byte[] bobWithPetName = sop.certifyUserId() + .userId("Bobby") + .noRequireSelfSig() + .keys(aliceKey) + .certs(bobCert) + .getBytes(); + + assertTrue(sop.validateUserId() + .userId("Bobby") + .authorities(aliceCert) + .subjects(bobWithPetName)); + } +} From ebf5866dbdf19308a242b1aed22a913039a79b3a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 14:17:38 +0200 Subject: [PATCH 24/29] Improve test --- .../operation/CertifyValidateUserIdTest.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) 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 index eabda44..488db9a 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java @@ -49,13 +49,15 @@ public class CertifyValidateUserIdTest { assertTrue(sop.validateUserId() .authorities(aliceCert) .userId("Alice ") - .subjects(aliceCert)); + .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)); + .subjects(bobCert), + "Alice has not yet certified Bobs user-id"); byte[] bobCertifiedByAlice = sop.certifyUserId() .userId("Bob ") @@ -67,7 +69,8 @@ public class CertifyValidateUserIdTest { assertTrue(sop.validateUserId() .userId("Bob ") .authorities(aliceCert) - .subjects(bobCertifiedByAlice)); + .subjects(bobCertifiedByAlice), + "Alice accepts Bobs user-id after she certified it"); } @ParameterizedTest @@ -94,7 +97,8 @@ public class CertifyValidateUserIdTest { .userId("Bobby") .keys(aliceKey) .certs(bobCert) - .getBytes()); + .getBytes(), + "Alice cannot create a pet-name for Bob without the --no-require-self-sig flag"); byte[] bobWithPetName = sop.certifyUserId() .userId("Bobby") @@ -106,6 +110,13 @@ public class CertifyValidateUserIdTest { assertTrue(sop.validateUserId() .userId("Bobby") .authorities(aliceCert) - .subjects(bobWithPetName)); + .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"); } } From 3f1c0fa54f833e93c7c7450e62570284e8a2aa0d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 14:36:28 +0200 Subject: [PATCH 25/29] SOP update-key: Rename --no-new-mechanisms option to --no-added-capabilities --- .../main/kotlin/sop/external/operation/UpdateKeyExternal.kt | 2 +- .../main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt | 6 +++--- sop-java/src/main/kotlin/sop/operation/UpdateKey.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) 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 9aa1d29..99df15e 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 noNewMechanisms(): UpdateKey = apply { commandList.add("--no-new-mechanisms") } + override fun noAddedCapabilities(): UpdateKey = apply { commandList.add("--no-added-capabilities") } 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/commands/UpdateKeyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt index 08f9297..931f241 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-new-mechanisms"]) var noNewMechanisms = false + @Option(names = ["--no-added-capabilities"]) var noAddedCapabilities = false @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") var withKeyPassword: List = listOf() @@ -38,8 +38,8 @@ class UpdateKeyCmd : AbstractSopCmd() { updateKey.signingOnly() } - if (noNewMechanisms) { - updateKey.noNewMechanisms() + if (noAddedCapabilities) { + updateKey.noAddedCapabilities() } for (passwordFileName in withKeyPassword) { diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt index 6c32b22..f0a7c52 100644 --- a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -21,7 +21,7 @@ interface UpdateKey { @Throws(SOPGPException.UnsupportedOption::class) fun signingOnly(): UpdateKey - @Throws(SOPGPException.UnsupportedOption::class) fun noNewMechanisms(): UpdateKey + @Throws(SOPGPException.UnsupportedOption::class) fun noAddedCapabilities(): UpdateKey @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) fun withKeyPassword(password: String): UpdateKey = From cc250efc56575ed968dda05b31cef672e960a6f3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 14:36:49 +0200 Subject: [PATCH 26/29] Document update key --- .../main/kotlin/sop/operation/UpdateKey.kt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt index f0a7c52..9a31310 100644 --- a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -19,25 +19,61 @@ 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 + /** + * 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, @@ -45,6 +81,12 @@ 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, From 9a23ec6bb0924f8a08d2378af0644d0f0330a331 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 15:26:00 +0200 Subject: [PATCH 27/29] Add test for certifying with revoked key --- .../operation/CertifyValidateUserIdTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) 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 index 488db9a..e05923c 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java @@ -119,4 +119,35 @@ public class CertifyValidateUserIdTest { .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()); + } } From f2d40bba17f7ae0de4e5cd435250173b4f0a29ef Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 10:58:58 +0200 Subject: [PATCH 28/29] Fix formatting issues --- .../kotlin/sop/external/operation/UpdateKeyExternal.kt | 4 +++- sop-java/src/main/kotlin/sop/operation/UpdateKey.kt | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) 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..b84f452 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,9 @@ 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 noAddedCapabilities(): UpdateKey = apply { + commandList.add("--no-added-capabilities") + } override fun withKeyPassword(password: ByteArray): UpdateKey = apply { commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCount") diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt index 9a31310..1226ed5 100644 --- a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -20,14 +20,14 @@ 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. + * 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. + * 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 From 2d9a5646bb22f46682c280b556de5cac3908e559 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 6 May 2025 12:18:05 +0200 Subject: [PATCH 29/29] Add test for certifying without ASCII armor --- .../operation/CertifyValidateUserIdTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) 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 index e05923c..c97fda7 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java @@ -73,6 +73,45 @@ public class CertifyValidateUserIdTest { "Alice accepts Bobs user-id after she certified it"); } + @ParameterizedTest + @MethodSource("provideInstances") + public void certifyUserIdUnarmored(SOP sop) throws IOException { + byte[] aliceKey = sop.generateKey() + .noArmor() + .withKeyPassword("sw0rdf1sh") + .userId("Alice ") + .generate() + .getBytes(); + byte[] aliceCert = sop.extractCert() + .noArmor() + .key(aliceKey) + .getBytes(); + + byte[] bobKey = sop.generateKey() + .noArmor() + .userId("Bob ") + .generate() + .getBytes(); + byte[] bobCert = sop.extractCert() + .noArmor() + .key(bobKey) + .getBytes(); + + byte[] bobCertifiedByAlice = sop.certifyUserId() + .noArmor() + .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 {