Move signature verification operations to sopv interface subset

This commit is contained in:
Paul Schaub 2024-03-17 15:45:14 +01:00
parent cd208c8942
commit ed9b2f5fef
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
10 changed files with 224 additions and 25 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,6 +10,7 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.opentest4j.TestAbortedException;
import sop.SOP;
import sop.exception.SOPGPException;
import java.util.stream.Stream;
@ -72,4 +73,17 @@ public class VersionTest extends AbstractSOPTest {
int sopRevision = sop.version().getSopSpecRevisionNumber();
assertTrue(sop.version().getSopSpecRevisionName().endsWith("" + sopRevision));
}
@ParameterizedTest
@MethodSource("provideInstances")
public void sopVVersionTest(SOP sop) {
try {
sop.version().getSopVVersion();
} catch (SOPGPException.UnsupportedOption e) {
throw new TestAbortedException(
"Implementation does (gracefully) not provide coverage for any sopv interface version.");
} catch (RuntimeException e) {
throw new TestAbortedException("Implementation does not provide coverage for any sopv interface version.");
}
}
}