Compare commits

...

39 commits

Author SHA1 Message Date
7c4b4a4ddb
Remove unused import 2025-05-13 15:37:48 +02:00
fd789c8652
Remove println statements 2025-05-13 15:36:36 +02:00
a71f4162a2
Add MergeCertsTest 2025-05-13 15:16:30 +02:00
563542b88a
Add test for certifying without ASCII armor 2025-05-13 14:23:49 +02:00
9a20b48f02
Fix formatting issues 2025-05-13 14:23:49 +02:00
c17a922594
Add test for certifying with revoked key 2025-05-13 14:23:49 +02:00
560da4fb8d
Document update key 2025-05-13 14:23:48 +02:00
655f9ac134
SOP update-key: Rename --no-new-mechanisms option to --no-added-capabilities 2025-05-13 14:23:48 +02:00
4b00369194
Improve test 2025-05-13 14:23:48 +02:00
3ccd83f795
Add basic test for certify-userid and validate-userid subcommands 2025-05-13 14:23:48 +02:00
61f2b93a5b
reuse: convert dep5 file to toml file 2025-05-13 14:23:47 +02:00
9fe49319f8
Add new Exception types 2025-05-13 14:23:26 +02:00
cedded2e79
Fix formatting 2025-05-13 14:23:25 +02:00
c09d548bea
MergeCertsCmd: Fix default value of armor 2025-05-13 14:23:25 +02:00
e306cf7345
validate-userid: Add --validate-at option 2025-05-13 14:23:25 +02:00
96593354e0
Remove call to explicitly set bundle to fix native image 2025-05-13 14:23:25 +02:00
b0ff1856a7
Fix documentation of merge-certs command 2025-05-13 14:23:25 +02:00
c8626e77ed
Bump version 2025-05-13 14:23:24 +02:00
91131f114d
Document endOfOptionsDelimiter 2025-05-13 14:23:01 +02:00
0d8f6d7f10
Fix nullability of sop commands 2025-05-13 14:23:01 +02:00
d490ada270
Add first test for new commands 2025-05-13 14:23:01 +02:00
1254a867a7
Show endOfOptions delimiter in help 2025-05-13 14:23:01 +02:00
3d2adab35d
Implement external variants of new subcommands 2025-05-13 14:23:01 +02:00
8e3f7ecd4d
Checkstyle 2025-05-13 14:23:00 +02:00
f4fe1cdac9
Implement validate-userid command 2025-05-13 14:23:00 +02:00
fe431070a4
Update msg files with input/output information 2025-05-13 14:23:00 +02:00
48689ca406
Checkstyle and exception handling improvements 2025-05-13 14:23:00 +02:00
158cf28412
Implement certify-userid command 2025-05-13 14:23:00 +02:00
62d9cd1991
Add support for rendering help info for input and output 2025-05-13 14:22:59 +02:00
a98afb1755
Add implementation of merge-certs command 2025-05-13 14:22:59 +02:00
bd692c7309
Add implementation of update-key command 2025-05-13 14:22:59 +02:00
aa8c2be25a
Add new exceptions 2025-05-13 14:22:58 +02:00
cbeec9c90d
SOP-Java 10.1.2-SNAPSHOT 2025-04-14 11:44:22 +02:00
701f9453ca
SOP-Java 10.1.1 2025-04-14 11:41:50 +02:00
2d99aea4ab
Bump animalsniffer to 2.0.0 2025-04-14 11:29:04 +02:00
4d2876a296
Fix formatting issue 2025-04-14 11:28:14 +02:00
e3fe9410d7
reuse: Migrate to toml format 2025-04-14 11:27:54 +02:00
a2a3bda2b3
Migrate AbortOnUnsupportedOption annotation back to java 2025-04-14 11:26:09 +02:00
cddc92bd92
Update changelog 2025-04-14 11:13:40 +02:00
73 changed files with 1692 additions and 150 deletions

View file

@ -1,29 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: SOP-Java
Upstream-Contact: Paul Schaub <info@pgpainless.org>
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

View file

@ -6,9 +6,12 @@ SPDX-License-Identifier: Apache-2.0
# Changelog # Changelog
## 10.1.1-SNAPSHOT ## 10.1.1
- Prepare jar files for use in native images, e.g. using GraalVM by generating and including - Prepare jar files for use in native images, e.g. using GraalVM by generating and including
configuration files for reflection, resources and dynamic proxies. configuration files for reflection, resources and dynamic proxies.
- gradle: Make use of jvmToolchain functionality
- gradle: Improve reproducibility
- gradle: Bump animalsniffer to `2.0.0`
## 10.1.0 ## 10.1.0
- `sop-java`: - `sop-java`:

32
REUSE.toml Normal file
View file

@ -0,0 +1,32 @@
# SPDX-FileCopyrightText: 2025 Paul Schaub <info@pgpainless.org>
#
# SPDX-License-Identifier: CC0-1.0
version = 1
SPDX-PackageName = "SOP-Java"
SPDX-PackageSupplier = "Paul Schaub <info@pgpainless.org>"
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"

View file

@ -18,7 +18,7 @@ buildscript {
} }
plugins { plugins {
id 'ru.vyarus.animalsniffer' version '1.5.3' id 'ru.vyarus.animalsniffer' version '2.0.0'
id 'org.jetbrains.kotlin.jvm' version "1.9.21" id 'org.jetbrains.kotlin.jvm' version "1.9.21"
id 'com.diffplug.spotless' version '6.22.0' apply false id 'com.diffplug.spotless' version '6.22.0' apply false
} }

View file

@ -69,6 +69,14 @@ class ExternalSOP(
override fun changeKeyPassword(): ChangeKeyPassword = override fun changeKeyPassword(): ChangeKeyPassword =
ChangeKeyPasswordExternal(binaryName, properties) 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 * 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 * temporarily store additional results of OpenPGP operations such that the binding classes can
@ -169,6 +177,18 @@ class ExternalSOP(
UnsupportedProfile.EXIT_CODE -> UnsupportedProfile.EXIT_CODE ->
throw UnsupportedProfile( throw UnsupportedProfile(
"External SOP backend reported error UnsupportedProfile ($exitCode):\n$errorMessage") "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? // Did you forget to add a case for a new exception type?
else -> else ->

View file

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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<String> = 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)
}

View file

@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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)
}

View file

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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 noAddedCapabilities(): UpdateKey = apply {
commandList.add("--no-added-capabilities")
}
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)
}

View file

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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
import sop.util.UTCUtil
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<String> = 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
}
override fun validateAt(date: Date): ValidateUserId = apply {
commandList.add("--validate-at=${UTCUtil.formatUTCDate(date)}")
}
}

View file

@ -27,6 +27,10 @@ import sop.exception.SOPGPException
ChangeKeyPasswordCmd::class, ChangeKeyPasswordCmd::class,
RevokeKeyCmd::class, RevokeKeyCmd::class,
ExtractCertCmd::class, ExtractCertCmd::class,
UpdateKeyCmd::class,
MergeCertsCmd::class,
CertifyUserIdCmd::class,
ValidateUserIdCmd::class,
// Messaging subcommands // Messaging subcommands
SignCmd::class, SignCmd::class,
VerifyCmd::class, VerifyCmd::class,
@ -83,6 +87,12 @@ class SopCLI {
.apply { .apply {
// Hide generate-completion command // Hide generate-completion command
subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true) 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 // overwrite executable name
commandName = EXECUTABLE_NAME commandName = EXECUTABLE_NAME
// setup exception handling // setup exception handling

View file

@ -7,6 +7,11 @@ package sop.cli.picocli.commands
import java.io.* import java.io.*
import java.text.ParseException import java.text.ParseException
import java.util.* 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.cli.picocli.commands.AbstractSopCmd.EnvironmentVariableResolver
import sop.exception.SOPGPException.* import sop.exception.SOPGPException.*
import sop.util.UTCUtil.Companion.parseUTCDate 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<String?, String?>) :
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<String>, header: String, details: String): List<String> {
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 { companion object {
const val PRFX_ENV = "@ENV:" const val PRFX_ENV = "@ENV:"
const val PRFX_FD = "@FD:" 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 val DAWN_OF_TIME = Date(0)
@JvmField @JvmField

View file

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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.BadData
import sop.exception.SOPGPException.UnsupportedOption
@Command(
name = "certify-userid",
resourceBundle = "msg_certify-userid",
exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE,
showEndOfOptionsDelimiterInUsageHelp = true)
class CertifyUserIdCmd : AbstractSopCmd() {
@Option(names = ["--no-armor"], negatable = true) var armor = true
@Option(names = ["--userid"], required = true, arity = "1..*", paramLabel = "USERID")
var userIds: List<String> = listOf()
@Option(names = ["--with-key-password"], paramLabel = "PASSWORD")
var withKeyPassword: List<String> = listOf()
@Option(names = ["--no-require-self-sig"]) var noRequireSelfSig = false
@Parameters(paramLabel = "KEYS", arity = "1..*") var keys: List<String> = 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)
}
}
}

View file

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
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
@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 = true
@CommandLine.Parameters(paramLabel = "CERTS") var updates: List<String> = 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)
}
}
}

View file

@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
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.*
@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-added-capabilities"]) var noAddedCapabilities = false
@Option(names = ["--with-key-password"], paramLabel = "PASSWORD")
var withKeyPassword: List<String> = listOf()
@Option(names = ["--merge-certs"], paramLabel = "CERTS") var mergeCerts: List<String> = listOf()
override fun run() {
val updateKey = throwIfUnsupportedSubcommand(SopCLI.getSop().updateKey(), "update-key")
if (!armor) {
updateKey.noArmor()
}
if (signingOnly) {
updateKey.signingOnly()
}
if (noAddedCapabilities) {
updateKey.noAddedCapabilities()
}
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 { updateKey.mergeCerts(it) }
} 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)
} catch (badData: BadData) {
val errorMsg = getMsg("sop.error.input.not_a_private_key", "STDIN")
throw BadData(errorMsg, badData)
}
}
}

View file

@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
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
@Command(
name = "validate-userid",
resourceBundle = "msg_validate-userid",
exitCodeOnInvalidInput = SOPGPException.MissingArg.EXIT_CODE,
showEndOfOptionsDelimiterInUsageHelp = true)
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")
var authorities: List<String> = listOf()
override fun run() {
val validateUserId =
throwIfUnsupportedSubcommand(SopCLI.getSop().validateUserId(), "validate-userid")
if (addrSpecOnly) {
validateUserId.addrSpecOnly()
}
if (validateAt != null) {
validateUserId.validateAt(validateAt!!)
}
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)
}
}
}

View file

@ -3,9 +3,13 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
usage.header=Add ASCII Armor to standard input 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.synopsisHeading=Usage:\u0020 usage.synopsisHeading=Usage:\u0020
usage.commandListHeading = %nCommands:%n
usage.optionListHeading=%nOptions:%n usage.optionListHeading=%nOptions:%n
usage.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -3,9 +3,11 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
usage.header=Schütze Standard-Eingabe mit ASCII Armor 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 stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.synopsisHeading=Aufruf:\u0020 usage.synopsisHeading=Aufruf:\u0020
usage.commandListHeading=%nBefehle:%n
usage.optionListHeading=%nOptionen:%n usage.optionListHeading=%nOptionen:%n
usage.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
#
# 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
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
usage.synopsisHeading=Usage:\u0020
usage.commandListHeading=%nCommands:%n
usage.optionListHeading=%nOptions:%n
usage.footerHeading=Powered by picocli%n

View file

@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
#
# 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
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
usage.commandListHeading=%nBefehle:%n
usage.optionListHeading=%nOptionen:%n
usage.footerHeading=Powered by Picocli%n

View file

@ -12,6 +12,11 @@ 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.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...). 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.descriptionHeading=%nDescription:%n usage.descriptionHeading=%nDescription:%n

View file

@ -12,6 +12,9 @@ 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.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...). 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 stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.descriptionHeading=%nBeschreibung:%n usage.descriptionHeading=%nBeschreibung:%n

View file

@ -3,6 +3,11 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
usage.header=Remove ASCII Armor from standard input 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.synopsisHeading=Usage:\u0020 usage.synopsisHeading=Usage:\u0020

View file

@ -3,6 +3,9 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
usage.header=Entferne ASCII Armor von Standard-Eingabe 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 stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.synopsisHeading=Aufruf:\u0020 usage.synopsisHeading=Aufruf:\u0020

View file

@ -22,6 +22,11 @@ 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...). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
KEY[0..*]=Secret keys to attempt decryption with KEY[0..*]=Secret keys to attempt decryption with
standardInput=CIPHERTEXT
standardInputDescription=Encrypted OpenPGP message
standardOutput=DATA
standardOutputDescription=Decrypted OpenPGP message
stacktrace=Print stacktrace stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameters:%n usage.parameterListHeading=%nParameters:%n

View file

@ -22,6 +22,9 @@ with-key-password.0=Passwort zum Entsperren der privaten Schl
with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...).
KEY[0..*]=Private Schlüssel zum Entschlüsseln der Nachricht KEY[0..*]=Private Schlüssel zum Entschlüsseln der Nachricht
standardInputDescription=Verschlüsselte OpenPGP Nachricht
standardOutputDescription=Entschlüsselte OpenPGP Nachricht
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameter:%n usage.parameterListHeading=%nParameter:%n

View file

@ -11,6 +11,11 @@ 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). 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 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameters:%n usage.parameterListHeading=%nParameters:%n

View file

@ -11,6 +11,9 @@ 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. 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 KEYS[0..*]=Private Signaturschlüssel
standardInputDescription=Daten die signiert werden sollen
standardOutputDescription=Abgetrennte OpenPGP Signatur(en)
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameter:%n usage.parameterListHeading=%nParameter:%n

View file

@ -13,6 +13,11 @@ not-after.3=Accepts special value "-" for end of time.
SIGNATURE[0]=Detached signature SIGNATURE[0]=Detached signature
CERT[1..*]=Public key certificates for signature verification 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.descriptionHeading=%nDescription:%n usage.descriptionHeading=%nDescription:%n

View file

@ -13,6 +13,9 @@ not-after.3=Akzeptiert speziellen Wert '-' f
SIGNATURE[0]=Abgetrennte Signatur SIGNATURE[0]=Abgetrennte Signatur
CERT[1..*]=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung 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 stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.descriptionHeading=%nBeschreibung:%n usage.descriptionHeading=%nBeschreibung:%n

View file

@ -12,6 +12,11 @@ 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...). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
CERTS[0..*]=Certificates the message gets encrypted to 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameters:%n usage.parameterListHeading=%nParameters:%n

View file

@ -12,6 +12,9 @@ with-key-password.0=Passwort zum Entsperren der privaten Schl
with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...).
CERTS[0..*]=Zertifikate für die die Nachricht verschlüsselt werden soll 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 stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameter:%n usage.parameterListHeading=%nParameter:%n

View file

@ -5,6 +5,11 @@ 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. usage.description=Read a secret key from STDIN and emit the public key certificate to STDOUT.
no-armor=ASCII armor the output 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.descriptionHeading=%nDescription:%n usage.descriptionHeading=%nDescription:%n

View file

@ -5,6 +5,9 @@ usage.header=Extrahiere Zertifikat (
usage.description=Lese einen Schlüssel von Standard-Eingabe und gebe das Zertifikat auf Standard-Ausgabe aus. 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 no-armor=Schütze Ausgabe mit ASCII Armor
standardInputDescription=Private Schlüssel, deren Zertifikate extrahiert werden sollen
standardOutputDescription=Extrahierte Zertifikate
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.descriptionHeading=%nBeschreibung:%n usage.descriptionHeading=%nBeschreibung:%n

View file

@ -9,6 +9,9 @@ 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.0=Password to protect the private key with
with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameters:%n usage.parameterListHeading=%nParameters:%n

View file

@ -9,6 +9,8 @@ signing-only=Generiere einen Schl
with-key-password.0=Passwort zum Schutz des privaten Schlüssels with-key-password.0=Passwort zum Schutz des privaten Schlüssels
with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...).
standardOutputDescription=Erzeugter OpenPGP Schlüssel
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameter:%n usage.parameterListHeading=%nParameter:%n

View file

@ -5,6 +5,11 @@ usage.header=Split signatures from a clearsigned message
no-armor=ASCII armor the output no-armor=ASCII armor the output
signatures-out=Destination to which a detached signatures block will be written 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.synopsisHeading=Usage:\u0020 usage.synopsisHeading=Usage:\u0020

View file

@ -5,6 +5,9 @@ usage.header=Trenne Signaturen von Klartext-signierter Nachricht
no-armor=Schütze Ausgabe mit ASCII Armor no-armor=Schütze Ausgabe mit ASCII Armor
signatures-out=Schreibe abgetrennte Signaturen in Ausgabe signatures-out=Schreibe abgetrennte Signaturen in Ausgabe
standardInputDescription=Klartext-signierte OpenPGP Nachricht
standardOutputDescription=Nachricht ohne Signaturen
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.synopsisHeading=Aufruf:\u0020 usage.synopsisHeading=Aufruf:\u0020

View file

@ -13,6 +13,11 @@ 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). 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 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameters:%n usage.parameterListHeading=%nParameters:%n

View file

@ -13,6 +13,9 @@ 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. 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 KEYS[0..*]=Private Signaturschlüssel
standardInputDescription=Daten, die signiert werden sollen
standardOutputDescription=Inline-signierte OpenPGP Nachricht
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameter:%n usage.parameterListHeading=%nParameter:%n

View file

@ -12,6 +12,11 @@ not-after.3=Accepts special value "-" for end of time.
verifications-out=File to write details over successful verifications to verifications-out=File to write details over successful verifications to
CERT[0..*]=Public key certificates for signature verification 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameters:%n usage.parameterListHeading=%nParameters:%n

View file

@ -12,6 +12,9 @@ not-after.3=Akzeptiert speziellen Wert '-' f
verifications-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe verifications-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe
CERT[0..*]=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung CERT[0..*]=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung
standardInputDescription=Inline-signierte OpenPGP Nachricht
standardOutputDescription=Nachricht ohne Signaturen
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameter:%n usage.parameterListHeading=%nParameter:%n

View file

@ -4,6 +4,9 @@
usage.header=Emit a list of profiles supported by the identified subcommand usage.header=Emit a list of profiles supported by the identified subcommand
subcommand=Subcommand for which to list profiles subcommand=Subcommand for which to list profiles
standardOutput=PROFILELIST
standardOutputDescription=List of profiles supported by the identified subcommand
stacktrace=Print stacktrace stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameters:%n usage.parameterListHeading=%nParameters:%n

View file

@ -4,6 +4,8 @@
usage.header=Gebe eine Liste von Profilen aus, welche vom angegebenen Unterbefehl unterstützt werden usage.header=Gebe eine Liste von Profilen aus, welche vom angegebenen Unterbefehl unterstützt werden
subcommand=Unterbefehl, für welchen Profile gelistet werden sollen subcommand=Unterbefehl, für welchen Profile gelistet werden sollen
standardOutputDescription=Liste von Profilen, die der identifizierte Unterbefehl unterstützt
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.parameterListHeading=%nParameter:%n usage.parameterListHeading=%nParameter:%n

View file

@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
#
# SPDX-License-Identifier: Apache-2.0
usage.headerHeading=Merge OpenPGP certificates%n
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
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.descriptionHeading=%nNote:%n
usage.commandListHeading=%nCommands:%n
usage.optionListHeading=%nOptions:%n
usage.footerHeading=Powered by picocli%n

View file

@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
#
# SPDX-License-Identifier: Apache-2.0
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
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=%nHinweis:%n
usage.commandListHeading=%nBefehle:%n
usage.optionListHeading=%nOptionen:%n
usage.footerHeading=Powered by Picocli%n

View file

@ -7,9 +7,14 @@ no-armor=ASCII armor the output
with-key-password.0=Passphrase to unlock the secret key(s). 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...). 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 stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.descriptionHeading=%nDescription:%n usage.descriptionHeading=D%nescription:%n
usage.synopsisHeading=Usage:\u0020 usage.synopsisHeading=Usage:\u0020
usage.commandListHeading=%nCommands:%n usage.commandListHeading=%nCommands:%n
usage.optionListHeading=%nOptions:%n usage.optionListHeading=%nOptions:%n

View file

@ -7,6 +7,9 @@ no-armor=Sch
with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.0=Passwort zum Entsperren der privaten Schlüssel
with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). 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 stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.descriptionHeading=%nBeschreibung:%n usage.descriptionHeading=%nBeschreibung:%n

View file

@ -11,8 +11,12 @@ locale=Locale for description texts
usage.synopsisHeading=Usage:\u0020 usage.synopsisHeading=Usage:\u0020
usage.commandListHeading=%nCommands:%n usage.commandListHeading=%nCommands:%n
usage.optionListHeading=%nOptions:%n usage.optionListHeading=%nOptions:%n
usage.parameterListHeading=%nParameters:%n
usage.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n
standardInputHeading=%nInput:%n
standardOutputHeading=%nOutput:%n
# Exit Codes # Exit Codes
usage.exitCodeListHeading=%nExit Codes:%n usage.exitCodeListHeading=%nExit Codes:%n
usage.exitCodeList.0=\u00200:Successful program execution usage.exitCodeList.0=\u00200:Successful program execution
@ -38,6 +42,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.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.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.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 ## SHARED RESOURCES
stacktrace=Print stacktrace stacktrace=Print stacktrace
@ -74,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_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.no_verifiable_signature_found=No verifiable signature found.
sop.error.runtime.cannot_decrypt_message=Message could not be decrypted. 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 ## Usage errors
sop.error.usage.password_or_cert_required=At least one password file or cert file required for encryption. 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. sop.error.usage.argument_required=Argument '%s' is required.

View file

@ -11,8 +11,12 @@ locale=Gebietsschema f
usage.synopsisHeading=Aufruf:\u0020 usage.synopsisHeading=Aufruf:\u0020
usage.commandListHeading=%nBefehle:%n usage.commandListHeading=%nBefehle:%n
usage.optionListHeading=%nOptionen:%n usage.optionListHeading=%nOptionen:%n
usage.parameterListHeading=%nParameter:%n
usage.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n
standardInputHeading=%nEingabe:%n
standardOutputHeading=%nAusgabe:%n
# Exit Codes # Exit Codes
usage.exitCodeListHeading=%nExit Codes:%n usage.exitCodeListHeading=%nExit Codes:%n
usage.exitCodeList.0=\u00200:Erfolgreiche Programmausführung usage.exitCodeList.0=\u00200:Erfolgreiche Programmausführung
@ -38,6 +42,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.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.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.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 ## SHARED RESOURCES
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben

View file

@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
#
# 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
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.footerHeading=Powered by picocli%n

View file

@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
#
# 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
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.footerHeading=Powered by Picocli%n

View file

@ -0,0 +1,20 @@
# SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
#
# 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
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
usage.synopsisHeading=Usage:\u0020
usage.commandListHeading=%nCommands:%n
usage.optionListHeading=%nOptions:%n
usage.footerHeading=Powered by picocli%n

View file

@ -0,0 +1,20 @@
# SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
#
# 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
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
usage.synopsisHeading=Aufruf:\u0020
usage.commandListHeading=%nBefehle:%n
usage.optionListHeading=%nOptionen:%n
usage.footerHeading=Powered by Picocli%n

View file

@ -6,6 +6,8 @@ extended=Print an extended version string
backend=Print information about the cryptographic backend backend=Print information about the cryptographic backend
sop-spec=Print the latest revision of the SOP specification targeted by the implementation sop-spec=Print the latest revision of the SOP specification targeted by the implementation
standardOutput=version information
stacktrace=Print stacktrace stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.synopsisHeading=Usage:\u0020 usage.synopsisHeading=Usage:\u0020

View file

@ -6,6 +6,8 @@ extended=Gebe erweiterte Versionsinformationen aus
backend=Gebe Informationen über das kryptografische Backend 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 sop-spec=Gebe die neuste Revision der SOP Spezifikation aus, welche von dieser Implementierung umgesetzt wird
standardOutput=Versionsinformationen
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0
usage.synopsisHeading=Aufruf:\u0020 usage.synopsisHeading=Aufruf:\u0020

View file

@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test;
import sop.SOP; import sop.SOP;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
import sop.operation.Armor; import sop.operation.Armor;
import sop.operation.CertifyUserId;
import sop.operation.ChangeKeyPassword; import sop.operation.ChangeKeyPassword;
import sop.operation.Dearmor; import sop.operation.Dearmor;
import sop.operation.Decrypt; import sop.operation.Decrypt;
@ -29,7 +30,10 @@ import sop.operation.InlineVerify;
import sop.operation.DetachedSign; import sop.operation.DetachedSign;
import sop.operation.DetachedVerify; import sop.operation.DetachedVerify;
import sop.operation.ListProfiles; import sop.operation.ListProfiles;
import sop.operation.MergeCerts;
import sop.operation.RevokeKey; import sop.operation.RevokeKey;
import sop.operation.UpdateKey;
import sop.operation.ValidateUserId;
import sop.operation.Version; import sop.operation.Version;
public class SOPTest { public class SOPTest {
@ -52,6 +56,26 @@ public class SOPTest {
@Test @Test
public void UnsupportedSubcommandsTest() { public void UnsupportedSubcommandsTest() {
SOP nullCommandSOP = new SOP() { SOP nullCommandSOP = new SOP() {
@Override
public ValidateUserId validateUserId() {
return null;
}
@Override
public CertifyUserId certifyUserId() {
return null;
}
@Override
public MergeCerts mergeCerts() {
return null;
}
@Override
public UpdateKey updateKey() {
return null;
}
@Override @Override
public Version version() { public Version version() {
return null; return null;
@ -140,6 +164,11 @@ public class SOPTest {
commands.add(new String[] {"sign"}); commands.add(new String[] {"sign"});
commands.add(new String[] {"verify", "signature.asc", "cert.asc"}); commands.add(new String[] {"verify", "signature.asc", "cert.asc"});
commands.add(new String[] {"version"}); commands.add(new String[] {"version"});
commands.add(new String[] {"list-profiles", "generate-key"});
commands.add(new String[] {"certify-userid", "--userid", "Alice <alice@pgpainless.org>", "--", "alice.pgp"});
commands.add(new String[] {"validate-userid", "Alice <alice@pgpainless.org>", "bob.pgp", "--", "alice.pgp"});
commands.add(new String[] {"update-key"});
commands.add(new String[] {"merge-certs"});
for (String[] command : commands) { for (String[] command : commands) {
int exit = SopCLI.execute(command); int exit = SopCLI.execute(command);

View file

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface AbortOnUnsupportedOption {
}

View file

@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
import sop.exception.SOPGPException;
import java.lang.annotation.Annotation;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class AbortOnUnsupportedOptionExtension implements TestExecutionExceptionHandler {
@Override
public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable {
Class<?> testClass = extensionContext.getRequiredTestClass();
Annotation annotation = testClass.getAnnotation(AbortOnUnsupportedOption.class);
if (annotation != null && throwable instanceof SOPGPException.UnsupportedOption) {
assumeTrue(false, "Test aborted due to: " + throwable.getMessage());
}
throw throwable;
}
}

View file

@ -0,0 +1,192 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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<Arguments> provideInstances() {
return AbstractSOPTest.provideBackends();
}
@ParameterizedTest
@MethodSource("provideInstances")
public void certifyUserId(SOP sop) throws IOException {
byte[] aliceKey = sop.generateKey()
.withKeyPassword("sw0rdf1sh")
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] aliceCert = sop.extractCert()
.key(aliceKey)
.getBytes();
byte[] bobKey = sop.generateKey()
.userId("Bob <bob@pgpainless.org>")
.generate()
.getBytes();
byte[] bobCert = sop.extractCert()
.key(bobKey)
.getBytes();
// Alice has her own user-id self-certified
assertTrue(sop.validateUserId()
.authorities(aliceCert)
.userId("Alice <alice@pgpainless.org>")
.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 <bob@pgpainless.org>")
.subjects(bobCert),
"Alice has not yet certified Bobs user-id");
byte[] bobCertifiedByAlice = sop.certifyUserId()
.userId("Bob <bob@pgpainless.org>")
.withKeyPassword("sw0rdf1sh")
.keys(aliceKey)
.certs(bobCert)
.getBytes();
assertTrue(sop.validateUserId()
.userId("Bob <bob@pgpainless.org>")
.authorities(aliceCert)
.subjects(bobCertifiedByAlice),
"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 <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] aliceCert = sop.extractCert()
.noArmor()
.key(aliceKey)
.getBytes();
byte[] bobKey = sop.generateKey()
.noArmor()
.userId("Bob <bob@pgpainless.org>")
.generate()
.getBytes();
byte[] bobCert = sop.extractCert()
.noArmor()
.key(bobKey)
.getBytes();
byte[] bobCertifiedByAlice = sop.certifyUserId()
.noArmor()
.userId("Bob <bob@pgpainless.org>")
.withKeyPassword("sw0rdf1sh")
.keys(aliceKey)
.certs(bobCert)
.getBytes();
assertTrue(sop.validateUserId()
.userId("Bob <bob@pgpainless.org>")
.authorities(aliceCert)
.subjects(bobCertifiedByAlice),
"Alice accepts Bobs user-id after she certified it");
}
@ParameterizedTest
@MethodSource("provideInstances")
public void addPetName(SOP sop) throws IOException {
byte[] aliceKey = sop.generateKey()
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] aliceCert = sop.extractCert()
.key(aliceKey)
.getBytes();
byte[] bobKey = sop.generateKey()
.userId("Bob <bob@pgpainless.org>")
.generate()
.getBytes();
byte[] bobCert = sop.extractCert()
.key(bobKey)
.getBytes();
assertThrows(SOPGPException.CertUserIdNoMatch.class, () ->
sop.certifyUserId()
.userId("Bobby")
.keys(aliceKey)
.certs(bobCert)
.getBytes(),
"Alice cannot create a pet-name for Bob without the --no-require-self-sig flag");
byte[] bobWithPetName = sop.certifyUserId()
.userId("Bobby")
.noRequireSelfSig()
.keys(aliceKey)
.certs(bobCert)
.getBytes();
assertTrue(sop.validateUserId()
.userId("Bobby")
.authorities(aliceCert)
.subjects(bobWithPetName),
"Alice accepts the pet-name she gave to Bob");
assertFalse(sop.validateUserId()
.userId("Bobby")
.authorities(bobWithPetName)
.subjects(bobWithPetName),
"Bob does not accept the pet-name Alice gave him");
}
@ParameterizedTest
@MethodSource("provideInstances")
public void certifyWithRevokedKey(SOP sop) throws IOException {
byte[] aliceKey = sop.generateKey()
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] aliceRevokedCert = sop.revokeKey()
.keys(aliceKey)
.getBytes();
byte[] aliceRevokedKey = sop.updateKey()
.mergeCerts(aliceRevokedCert)
.key(aliceKey)
.getBytes();
byte[] bobKey = sop.generateKey()
.userId("Bob <bob@pgpainless.org>")
.generate()
.getBytes();
byte[] bobCert = sop.extractCert()
.key(bobKey)
.getBytes();
assertThrows(SOPGPException.KeyCannotCertify.class, () ->
sop.certifyUserId()
.userId("Bob <bob@pgpainless.org>")
.keys(aliceRevokedKey)
.certs(bobCert)
.getBytes());
}
}

View file

@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.operation;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import sop.SOP;
import java.io.IOException;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
public class MergeCertsTest extends AbstractSOPTest {
static Stream<Arguments> provideInstances() {
return provideBackends();
}
@ParameterizedTest
@MethodSource("provideInstances")
public void testMergeWithItself(SOP sop) throws IOException {
byte[] key = sop.generateKey()
.noArmor()
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] cert = sop.extractCert()
.noArmor()
.key(key)
.getBytes();
byte[] merged = sop.mergeCerts()
.noArmor()
.updates(cert)
.baseCertificates(cert)
.getBytes();
assertArrayEquals(cert, merged);
}
@ParameterizedTest
@MethodSource("provideInstances")
public void testApplyBaseToUpdate(SOP sop) throws IOException {
byte[] key = sop.generateKey()
.noArmor()
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] cert = sop.extractCert()
.noArmor()
.key(key)
.getBytes();
byte[] update = sop.revokeKey()
.noArmor()
.keys(key)
.getBytes();
byte[] merged = sop.mergeCerts()
.noArmor()
.updates(cert)
.baseCertificates(update)
.getBytes();
assertArrayEquals(update, merged);
}
@ParameterizedTest
@MethodSource("provideInstances")
public void testApplyUpdateToBase(SOP sop) throws IOException {
byte[] key = sop.generateKey()
.noArmor()
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] cert = sop.extractCert()
.noArmor()
.key(key)
.getBytes();
byte[] update = sop.revokeKey()
.noArmor()
.keys(key)
.getBytes();
byte[] merged = sop.mergeCerts()
.noArmor()
.updates(update)
.baseCertificates(cert)
.getBytes();
assertArrayEquals(update, merged);
}
}

View file

@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite
import java.lang.annotation.Inherited
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Inherited
annotation class AbortOnUnsupportedOption

View file

@ -1,21 +0,0 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler
import sop.exception.SOPGPException
class AbortOnUnsupportedOptionExtension : TestExecutionExceptionHandler {
override fun handleTestExecutionException(context: ExtensionContext, throwable: Throwable) {
val testClass = context.requiredTestClass
val annotation = testClass.getAnnotation(AbortOnUnsupportedOption::class.java)
if (annotation != null && SOPGPException.UnsupportedOption::class.isInstance(throwable)) {
Assumptions.assumeTrue(false, "Test aborted due to: " + throwable.message)
}
throw throwable
}
}

View file

@ -4,18 +4,7 @@
package sop package sop
import sop.operation.Armor import sop.operation.*
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
/** /**
* Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related * Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related
@ -26,48 +15,60 @@ import sop.operation.RevokeKey
interface SOP : SOPV { interface SOP : SOPV {
/** Generate a secret key. */ /** Generate a secret key. */
fun generateKey(): GenerateKey fun generateKey(): GenerateKey?
/** Extract a certificate (public key) from a secret key. */ /** 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. * 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. * 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 * Sign a message using inline signatures. If you need to create detached signatures, use
* [detachedSign] instead. * [detachedSign] instead.
*/ */
fun inlineSign(): InlineSign fun inlineSign(): InlineSign?
/** Detach signatures from an inline signed message. */ /** Detach signatures from an inline signed message. */
fun inlineDetach(): InlineDetach fun inlineDetach(): InlineDetach?
/** Encrypt a message. */ /** Encrypt a message. */
fun encrypt(): Encrypt fun encrypt(): Encrypt?
/** Decrypt a message. */ /** Decrypt a message. */
fun decrypt(): Decrypt fun decrypt(): Decrypt?
/** Convert binary OpenPGP data to ASCII. */ /** Convert binary OpenPGP data to ASCII. */
fun armor(): Armor fun armor(): Armor?
/** Converts ASCII armored OpenPGP data to binary. */ /** Converts ASCII armored OpenPGP data to binary. */
fun dearmor(): Dearmor fun dearmor(): Dearmor?
/** List supported [Profiles][Profile] of a subcommand. */ /** List supported [Profiles][Profile] of a subcommand. */
fun listProfiles(): ListProfiles fun listProfiles(): ListProfiles?
/** Revoke one or more secret keys. */ /** Revoke one or more secret keys. */
fun revokeKey(): RevokeKey fun revokeKey(): RevokeKey?
/** Update a key's password. */ /** Update a key's password. */
fun changeKeyPassword(): ChangeKeyPassword fun changeKeyPassword(): ChangeKeyPassword?
/** Keep a secret key up-to-date. */
fun updateKey(): UpdateKey?
/** Merge OpenPGP certificates. */
fun mergeCerts(): MergeCerts?
/** Certify OpenPGP Certificate User-IDs. */
fun certifyUserId(): CertifyUserId?
/** Validate a UserID in an OpenPGP certificate. */
fun validateUserId(): ValidateUserId?
} }

View file

@ -12,23 +12,23 @@ import sop.operation.Version
interface SOPV { interface SOPV {
/** Get information about the implementations name and version. */ /** 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 * Verify detached signatures. If you need to verify an inline-signed message, use
* [inlineVerify] instead. * [inlineVerify] instead.
*/ */
fun verify(): DetachedVerify = detachedVerify() fun verify(): DetachedVerify? = detachedVerify()
/** /**
* Verify detached signatures. If you need to verify an inline-signed message, use * Verify detached signatures. If you need to verify an inline-signed message, use
* [inlineVerify] instead. * [inlineVerify] instead.
*/ */
fun detachedVerify(): DetachedVerify fun detachedVerify(): DetachedVerify?
/** /**
* Verify signatures of an inline-signed message. If you need to verify detached signatures over * Verify signatures of an inline-signed message. If you need to verify detached signatures over
* a message, use [detachedVerify] instead. * a message, use [detachedVerify] instead.
*/ */
fun inlineVerify(): InlineVerify fun inlineVerify(): InlineVerify?
} }

View file

@ -9,9 +9,10 @@ package sop
* *
* @param micAlg string identifying the digest mechanism used to create the signed message. This is * @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 * 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). * object as described in section 5 of
* If more than one signature was generated and different digest mechanisms were used, the value * [RFC3156](https://www.rfc-editor.org/rfc/rfc3156#section-5). If more than one signature was
* of the micalg object is an empty string. * generated and different digest mechanisms were used, the value of the micalg object is an empty
* string.
*/ */
data class SigningResult(val micAlg: MicAlg) { data class SigningResult(val micAlg: MicAlg) {

View file

@ -16,6 +16,22 @@ abstract class SOPGPException : RuntimeException {
abstract fun getExitCode(): Int 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). */ /** No acceptable signatures found (sop verify, inline-verify). */
class NoSignature : SOPGPException { class NoSignature : SOPGPException {
@JvmOverloads @JvmOverloads
@ -337,4 +353,64 @@ abstract class SOPGPException : RuntimeException {
const val EXIT_CODE = 101 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 {
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
companion object {
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
}
}
} }

View file

@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.operation
import java.io.IOException
import java.io.InputStream
import sop.Ready
import sop.exception.SOPGPException
import sop.util.UTF8Util
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())
}

View file

@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.operation
import java.io.IOException
import java.io.InputStream
import sop.Ready
import sop.exception.SOPGPException
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())
}

View file

@ -0,0 +1,96 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.operation
import java.io.IOException
import java.io.InputStream
import sop.Ready
import sop.exception.SOPGPException
import sop.util.UTF8Util
interface UpdateKey {
/**
* Disable ASCII armor encoding of the output.
*
* @return builder instance
*/
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,
SOPGPException.KeyIsProtected::class,
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,
SOPGPException.KeyIsProtected::class,
SOPGPException.PrimaryKeyBad::class)
fun key(key: ByteArray): Ready = key(key.inputStream())
}

View file

@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.operation
import java.io.IOException
import java.io.InputStream
import java.util.*
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())
fun validateAt(date: Date): ValidateUserId
}

View file

@ -4,7 +4,7 @@
allprojects { allprojects {
ext { ext {
shortVersion = '10.1.1' shortVersion = '11.0.0'
isSnapshot = true isSnapshot = true
minAndroidSdk = 10 minAndroidSdk = 10
javaSourceCompatibility = 11 javaSourceCompatibility = 11