Compare commits

...

71 commits
14.0.0 ... main

Author SHA1 Message Date
b28e234f21
ExternalTestSuite: Add license header 2025-07-25 14:09:57 +02:00
cc08b76b68
Introduce sop-java-json-gson module 2025-07-25 14:09:06 +02:00
e680f3450a
SOPV: Document since when operations are available 2025-07-25 12:21:06 +02:00
d4e8c14b08
Update documentation, add withKeyPassphrase(CharArray) methods 2025-07-24 12:53:27 +02:00
d32d9b54d7
Depend on junit-platform-suite to avoid needing to inherit test suite for external-sop tests 2025-07-01 15:06:50 +02:00
c651adc0b3
Add VerificationAssert methods, disable tests for unsupported operations 2025-07-01 14:38:13 +02:00
b3223372c6
SOP-Java 14.0.1-SNAPSHOT 2025-06-17 13:45:10 +02:00
9762f1f043
SOP-Java 14.0.0 2025-06-17 13:45:10 +02:00
191ec8c07d
Improve CHANGELOG again 2025-06-17 13:45:09 +02:00
07d0aa6941
Update CHANGELOG 2025-06-17 13:45:09 +02:00
12835bfb8e
Add/fix missing localizations for new SOP commands 2025-06-17 13:45:09 +02:00
04d154f63d
Version: Fix getSopJavaVersion() 2025-06-17 13:45:09 +02:00
01be696f75
Bump version to 14.0.0-SNAPSHOT 2025-06-17 13:45:09 +02:00
86718a2690
Bump logback to 1.5.13 2025-06-17 13:45:08 +02:00
e72e5a15c0
Fix: Pass chars to StringBuilder.append() 2025-06-17 13:45:08 +02:00
ac17000ff1
Clean up unused version literal 2025-06-17 13:45:08 +02:00
2a22cea29b
Remove animalsniffer 2025-06-17 13:45:08 +02:00
8a7fd5cb58
Move validate-userid to SOPV 2025-06-17 13:45:08 +02:00
21766a1f39
Delete ProxyOutputStream and test 2025-06-17 13:45:07 +02:00
cdcbae7e5f
Add test for JSON data parsing and serializing using a dummy implementation 2025-06-17 13:45:07 +02:00
ebfde35422
Add support for JSON POJOs 2025-06-17 13:45:07 +02:00
bff4423f93
Verification: Rename description to jsonOrDescription 2025-06-17 13:45:07 +02:00
5a7a8ae901
MergeCertsTest: do not pass unarmored data
This is done to fix external-sop tests, which rely on environment variables,
which do not play nicely with binary data
2025-06-17 13:45:07 +02:00
79aece6f04
SOP, SOPV: Add --debug option 2025-06-17 13:45:07 +02:00
28d06c330d
ExternalSOP: Map UnspecificError 2025-06-17 13:45:06 +02:00
ab13cc1de1
CertifyUserIdExternal: add separator before passing keys 2025-06-17 13:45:06 +02:00
e1d048225b
CertifyValidateUserIdTest: unbound User-IDs do throw exceptions 2025-06-17 13:45:06 +02:00
61206dde53
GenerateKeyTest: Provoke exception for CertCannotEncrypt test case 2025-06-17 13:45:06 +02:00
589884672a
External-SOP: Properly map KeyCannotCertify error code 2025-06-17 13:45:06 +02:00
47a6db8702
External-SOP: Fix error message typo 2025-06-17 13:45:05 +02:00
0df80470c6
External-SOP: Extend test suite with new test classes 2025-06-17 13:45:05 +02:00
00a02686c8
External-SOP: Fix command names 2025-06-17 13:45:05 +02:00
c5d9e57f69
Add test for encrypt-decrypt using all available generate-key profiles 2025-06-17 13:45:05 +02:00
e481717421
Add Profile.withAliases() utility function 2025-06-17 13:45:05 +02:00
9677f1fd0b
Fix profile constructors 2025-06-17 13:45:05 +02:00
e5cb58468b
Add aliases to Profile 2025-06-17 13:45:04 +02:00
4ef5444e78
Test key generation with supported profiles 2025-06-17 13:45:04 +02:00
77106942d1
Add tests for MergeCerts command 2025-06-17 13:45:04 +02:00
6d23d3771d
Remove unused import 2025-06-17 13:45:04 +02:00
9360b0e8ce
Remove println statements 2025-06-17 13:45:04 +02:00
38c5a947dd
Add MergeCertsTest 2025-06-17 13:45:03 +02:00
be460fabab
Add test for certifying without ASCII armor 2025-06-17 13:45:03 +02:00
138e275bb6
Fix formatting issues 2025-06-17 13:45:03 +02:00
091b5f9a5e
Add test for certifying with revoked key 2025-06-17 13:45:03 +02:00
dea7e905a9
Document update key 2025-06-17 13:45:03 +02:00
8c077a9c13
SOP update-key: Rename --no-new-mechanisms option to --no-added-capabilities 2025-06-17 13:45:03 +02:00
a8cfb8fbf4
Improve test 2025-06-17 13:45:02 +02:00
a8497617d5
Add basic test for certify-userid and validate-userid subcommands 2025-06-17 13:45:02 +02:00
68bab9cbb4
reuse: convert dep5 file to toml file 2025-06-17 13:45:02 +02:00
65aa0afd4e
Add new Exception types 2025-06-17 13:45:02 +02:00
a72545e3b9
Fix formatting 2025-06-17 13:45:02 +02:00
082cbde869
MergeCertsCmd: Fix default value of armor 2025-06-17 13:45:01 +02:00
b300be42a4
validate-userid: Add --validate-at option 2025-06-17 13:45:01 +02:00
5105b6f4ad
Remove call to explicitly set bundle to fix native image 2025-06-17 13:45:01 +02:00
7f1c1b1aae
Fix documentation of merge-certs command 2025-06-17 13:45:01 +02:00
4cf410a9f9
Bump version 2025-06-17 13:45:01 +02:00
f1bdce99cb
Document endOfOptionsDelimiter 2025-06-17 13:45:00 +02:00
f7cc9ab816
Fix nullability of sop commands 2025-06-17 13:45:00 +02:00
40ccb8cc99
Add first test for new commands 2025-06-17 13:45:00 +02:00
0bb50952c5
Show endOfOptions delimiter in help 2025-06-17 13:45:00 +02:00
69fbfc09a7
Implement external variants of new subcommands 2025-06-17 13:45:00 +02:00
dd12e28926
Checkstyle 2025-06-17 13:45:00 +02:00
4ed326a142
Implement validate-userid command 2025-06-17 13:44:59 +02:00
122cd016a1
Update msg files with input/output information 2025-06-17 13:44:59 +02:00
2b6a5dd651
Checkstyle and exception handling improvements 2025-06-17 13:44:59 +02:00
d6c1330874
Implement certify-userid command 2025-06-17 13:44:59 +02:00
ada77be955
Add support for rendering help info for input and output 2025-06-17 13:44:59 +02:00
84404d629f
Add implementation of merge-certs command 2025-06-17 13:44:58 +02:00
4115a5041d
Add implementation of update-key command 2025-06-17 13:44:58 +02:00
1dcf13244d
Add new exceptions 2025-06-17 13:44:58 +02:00
ad137d6351
Try to fix coveralls repo token 2025-05-15 02:16:52 +02:00
128 changed files with 2840 additions and 586 deletions

View file

@ -17,5 +17,5 @@ steps:
# Code has coverage # Code has coverage
- gradle jacocoRootReport coveralls - gradle jacocoRootReport coveralls
environment: environment:
coveralls_repo_token: COVERALLS_REPO_TOKEN:
from_secret: coveralls_repo_token from_secret: coveralls_repo_token

View file

@ -6,6 +6,25 @@ SPDX-License-Identifier: Apache-2.0
# Changelog # Changelog
## 14.0.0
- Update implementation to [SOP Specification revision 14](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-14.html),
including changes from revisions `11`, `12`, `13`, `14`.
- Implement newly introduced operations
- `update-key` 'fixes' everything wrong with a key
- `merge-certs` merges a certificate with other copies
- `certify-userid` create signatures over user-ids on certificates
- `validate-userid` validate signatures over user-ids
- Add new exceptions
- `UnspecificFailure` maps generic application errors
- `KeyCannotCertify` signals that a key cannot be used for third-party certifications
- `NoHardwareKeyFound` signals that a key backed by a hardware device cannot be found
- `HardwareKeyFailure` signals a hardware device failure
- `PrimaryKeyBad` signals an unusable or bad primary key
- `CertUserIdNoMatch` signals that a user-id cannot be found/validated on a certificate
- `Verification`: Add support for JSON description extensions
- Remove `animalsniffer` from build dependencies
- Bump `logback` to `1.5.13`
## 10.1.1 ## 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.

View file

@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0
# SOP for Java # SOP for Java
[![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java) [![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java)
[![Spec Revision: 10](https://img.shields.io/badge/Spec%20Revision-10-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/10/) [![Spec Revision: 14](https://img.shields.io/badge/Spec%20Revision-10-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/14/)
[![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main) [![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main)
[![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java) [![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java)

View file

@ -1,6 +1,6 @@
# SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org> # SPDX-FileCopyrightText: 2025 Paul Schaub <info@pgpainless.org>
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: CC0-1.0
version = 1 version = 1
SPDX-PackageName = "SOP-Java" SPDX-PackageName = "SOP-Java"

View file

@ -18,7 +18,6 @@ buildscript {
} }
plugins { plugins {
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
} }
@ -35,18 +34,6 @@ allprojects {
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'com.diffplug.spotless' apply plugin: 'com.diffplug.spotless'
// For non-cli modules enable android api compatibility check
if (it.name.equals('sop-java')) {
// animalsniffer
apply plugin: 'ru.vyarus.animalsniffer'
dependencies {
signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature"
}
animalsniffer {
sourceSets = [sourceSets.main]
}
}
// Only generate jar for submodules // Only generate jar for submodules
// https://stackoverflow.com/a/25445035 // https://stackoverflow.com/a/25445035
jar { jar {

View file

@ -15,7 +15,9 @@ repositories {
dependencies { dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
testImplementation "org.junit.platform:junit-platform-suite-api:1.13.2"
testRuntimeOnly 'org.junit.platform:junit-platform-suite:1.13.2'
api project(":sop-java") api project(":sop-java")
api "org.slf4j:slf4j-api:$slf4jVersion" api "org.slf4j:slf4j-api:$slf4jVersion"

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
@ -112,6 +120,9 @@ class ExternalSOP(
val errorMessage = readString(errIn) val errorMessage = readString(errIn)
when (exitCode) { when (exitCode) {
UnspecificFailure.EXIT_CODE ->
throw UnspecificFailure(
"External SOP backend reported an unspecific error ($exitCode):\n$errorMessage")
NoSignature.EXIT_CODE -> NoSignature.EXIT_CODE ->
throw NoSignature( throw NoSignature(
"External SOP backend reported error NoSignature ($exitCode):\n$errorMessage") "External SOP backend reported error NoSignature ($exitCode):\n$errorMessage")
@ -169,6 +180,21 @@ 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 HardwareKeyFailure ($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")
KeyCannotCertify.EXIT_CODE ->
throw KeyCannotCertify(
"External SOP backend reported error KeyCannotCertify ($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

@ -10,9 +10,11 @@ import sop.SOPV
import sop.external.ExternalSOP.TempDirProvider import sop.external.ExternalSOP.TempDirProvider
import sop.external.operation.DetachedVerifyExternal import sop.external.operation.DetachedVerifyExternal
import sop.external.operation.InlineVerifyExternal import sop.external.operation.InlineVerifyExternal
import sop.external.operation.ValidateUserIdExternal
import sop.external.operation.VersionExternal import sop.external.operation.VersionExternal
import sop.operation.DetachedVerify import sop.operation.DetachedVerify
import sop.operation.InlineVerify import sop.operation.InlineVerify
import sop.operation.ValidateUserId
import sop.operation.Version import sop.operation.Version
/** /**
@ -37,6 +39,8 @@ class ExternalSOPV(
override fun inlineVerify(): InlineVerify = override fun inlineVerify(): InlineVerify =
InlineVerifyExternal(binaryName, properties, tempDirProvider) InlineVerifyExternal(binaryName, properties, tempDirProvider)
override fun validateUserId(): ValidateUserId = ValidateUserIdExternal(binaryName, properties)
companion object { companion object {
/** /**

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, "certify-userid")
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("--").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, "merge-certs")
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, "validate-userid")
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

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external;
import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
import org.junit.platform.suite.api.SuiteDisplayName;
@Suite
@SuiteDisplayName("External SOP Tests")
@SelectPackages("sop.testsuite.operation")
@IncludeClassNamePatterns(".*Test")
public class ExternalTestSuite {
}

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.ArmorDearmorTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalArmorDearmorTest extends ArmorDearmorTest {
}

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.DecryptWithSessionKeyTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalDecryptWithSessionKeyTest extends DecryptWithSessionKeyTest {
}

View file

@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.DetachedSignDetachedVerifyTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalDetachedSignDetachedVerifyTest extends DetachedSignDetachedVerifyTest {
}

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.EncryptDecryptTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalEncryptDecryptTest extends EncryptDecryptTest {
}

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.ExtractCertTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalExtractCertTest extends ExtractCertTest {
}

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.GenerateKeyTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalGenerateKeyTest extends GenerateKeyTest {
}

View file

@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.InlineSignInlineDetachDetachedVerifyTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalInlineSignInlineDetachDetachedVerifyTest
extends InlineSignInlineDetachDetachedVerifyTest {
}

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.InlineSignInlineVerifyTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalInlineSignInlineVerifyTest extends InlineSignInlineVerifyTest {
}

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.ListProfilesTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalListProfilesTest extends ListProfilesTest {
}

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.RevokeKeyTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalRevokeKeyTest extends RevokeKeyTest {
}

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.external.operation;
import org.junit.jupiter.api.condition.EnabledIf;
import sop.testsuite.operation.VersionTest;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ExternalVersionTest extends VersionTest {
}

View file

@ -7,5 +7,6 @@ rootProject.name = 'SOP-Java'
include 'sop-java', include 'sop-java',
'sop-java-picocli', 'sop-java-picocli',
'sop-java-testfixtures', 'sop-java-testfixtures',
'external-sop' 'external-sop',
'sop-java-json-gson'

View file

@ -0,0 +1,13 @@
<!--
SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
SPDX-License-Identifier: Apache-2.0
-->
# SOP-Java-JSON-GSON
## JSON Parsing VERIFICATION extension JSON using Gson
Since revision 11, the SOP specification defines VERIFICATIONS extension JSON.
This module implements the `JSONParser` and `JSONSerializer` interfaces using Googles Gson library.

View file

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
plugins {
id 'java-library'
}
group 'org.pgpainless'
repositories {
mavenCentral()
}
dependencies {
implementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
implementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
runtimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
implementation project(":sop-java")
api "org.slf4j:slf4j-api:$slf4jVersion"
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
// @Nonnull, @Nullable...
implementation "com.google.code.findbugs:jsr305:$jsrVersion"
api "com.google.code.gson:gson:$gsonVersion"
}

View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.google.gson.reflect.TypeToken
import java.text.ParseException
class GsonParser(
private val gson: Gson = Gson()
) : Verification.JSONParser {
override fun parse(string: String): Verification.JSON {
try {
return gson.fromJson(string, object : TypeToken<Verification.JSON>(){}.type)
} catch (e: JsonSyntaxException) {
throw ParseException(e.message, 0)
}
}
}

View file

@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop
import com.google.gson.Gson
class GsonSerializer(
private val gson: Gson = Gson()
) : Verification.JSONSerializer {
override fun serialize(json: Verification.JSON): String {
return gson.toJson(json)
}
}

View file

@ -0,0 +1,96 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.text.ParseException
class GsonSerializerAndParserTest {
private val serializer: GsonSerializer = GsonSerializer()
private val parser: GsonParser = GsonParser()
@Test
fun simpleSingleTest() {
val before = Verification.JSON("/tmp/alice.pgp")
val json = serializer.serialize(before)
assertEquals("{\"signers\":[\"/tmp/alice.pgp\"]}", json)
val after = parser.parse(json)
assertEquals(before, after)
}
@Test
fun simpleListTest() {
val before = Verification.JSON(listOf("/tmp/alice.pgp", "/tmp/bob.asc"))
val json = serializer.serialize(before)
assertEquals("{\"signers\":[\"/tmp/alice.pgp\",\"/tmp/bob.asc\"]}", json)
val after = parser.parse(json)
assertEquals(before, after)
}
@Test
fun withCommentTest() {
val before = Verification.JSON(
listOf("/tmp/alice.pgp"),
"This is a comment.",
null)
val json = serializer.serialize(before)
assertEquals("{\"signers\":[\"/tmp/alice.pgp\"],\"comment\":\"This is a comment.\"}", json)
val after = parser.parse(json)
assertEquals(before, after)
}
@Test
fun withExtStringTest() {
val before = Verification.JSON(
listOf("/tmp/alice.pgp"),
"This is a comment.",
"This is an ext object string.")
val json = serializer.serialize(before)
assertEquals("{\"signers\":[\"/tmp/alice.pgp\"],\"comment\":\"This is a comment.\",\"ext\":\"This is an ext object string.\"}", json)
val after = parser.parse(json)
assertEquals(before, after)
}
@Test
fun withExtListTest() {
val before = Verification.JSON(
listOf("/tmp/alice.pgp"),
"This is a comment.",
listOf(1.0,2.0,3.0))
val json = serializer.serialize(before)
assertEquals("{\"signers\":[\"/tmp/alice.pgp\"],\"comment\":\"This is a comment.\",\"ext\":[1.0,2.0,3.0]}", json)
val after = parser.parse(json)
assertEquals(before, after)
}
@Test
fun parseInvalidJSON() {
assertThrows<ParseException> { parser.parse("Invalid") }
}
@Test
fun parseMalformedJSON() {
// Missing '}'
assertThrows<ParseException> { parser.parse("{\"signers\":[\"Alice\"]") }
}
}

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,
@ -60,7 +64,7 @@ class SopCLI {
@JvmField var EXECUTABLE_NAME = "sop" @JvmField var EXECUTABLE_NAME = "sop"
@JvmField @JvmField
@Option(names = ["--stacktrace"], scope = ScopeType.INHERIT) @Option(names = ["--stacktrace", "--debug"], scope = ScopeType.INHERIT)
var stacktrace = false var stacktrace = false
@JvmStatic @JvmStatic
@ -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

@ -45,7 +45,8 @@ class SopVCLI {
@JvmField var EXECUTABLE_NAME = "sopv" @JvmField var EXECUTABLE_NAME = "sopv"
@JvmField @JvmField
@CommandLine.Option(names = ["--stacktrace"], scope = CommandLine.ScopeType.INHERIT) @CommandLine.Option(
names = ["--stacktrace", "--debug"], scope = CommandLine.ScopeType.INHERIT)
var stacktrace = false var stacktrace = false
@JvmStatic @JvmStatic

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,10 +12,15 @@ old-key-password.0=Old passwords to unlock the keys with.
old-key-password.1=Multiple passwords can be passed in, which are tested sequentially to unlock locked subkeys. old-key-password.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
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.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -12,10 +12,13 @@ old-key-password.0=Alte Passw
old-key-password.1=Mehrere Passwortkandidaten können übergeben werden, welche der Reihe nach durchprobiert werden, um Unterschlüssel zu entsperren. old-key-password.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
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.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -3,9 +3,14 @@
# 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
usage.commandListHeading = %nCommands:%n 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,12 @@
# 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
usage.commandListHeading=%nBefehle:%n 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

@ -22,10 +22,15 @@ with-key-password.0=Passphrase to unlock the secret key(s).
with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). 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
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.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -22,10 +22,13 @@ with-key-password.0=Passwort zum Entsperren der privaten Schl
with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). 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
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.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -11,10 +11,15 @@ with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, f
micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156). 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
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.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -11,10 +11,13 @@ with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable,
micalg-out=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. 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
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.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -13,11 +13,16 @@ 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
usage.parameterListHeading=%nParameters:%n usage.parameterListHeading=%nParameters:%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
usage.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -13,11 +13,14 @@ 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
usage.parameterListHeading=%nParameter:%n usage.parameterListHeading=%nParameter:%n
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.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -12,10 +12,15 @@ with-key-password.0=Passphrase to unlock the secret key(s).
with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). 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
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.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -12,10 +12,13 @@ with-key-password.0=Passwort zum Entsperren der privaten Schl
with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). 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
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.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -5,10 +5,15 @@ usage.header=Extract a public key certificate from a secret key
usage.description=Read a secret key from STDIN and emit the public key certificate to STDOUT. 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
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.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -5,10 +5,13 @@ usage.header=Extrahiere Zertifikat (
usage.description=Lese einen Schlüssel von Standard-Eingabe und gebe das Zertifikat auf Standard-Ausgabe aus. 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
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.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -9,10 +9,13 @@ signing-only=Generate a key that can only be used for signing
with-key-password.0=Password to protect the private key with with-key-password.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
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.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -9,10 +9,12 @@ 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
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.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -6,6 +6,6 @@ usage.header=Display usage information for the specified 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.synopsisHeading=Usage:\u0020 usage.synopsisHeading=Usage:\u0020
usage.commandListHeading = %nCommands:%n 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

@ -7,5 +7,5 @@ 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.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

@ -5,9 +5,14 @@ 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
usage.commandListHeading = %nCommands:%n 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

@ -5,9 +5,12 @@ 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
usage.commandListHeading=%nBefehle:%n 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

@ -13,10 +13,15 @@ with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, f
micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156). 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
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.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -13,10 +13,13 @@ with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable,
micalg=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. 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
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.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -12,10 +12,15 @@ 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
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.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -12,10 +12,13 @@ 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
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.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -4,10 +4,13 @@
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
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.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -4,10 +4,12 @@
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
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.footerHeading=Powered by Picocli%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.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,10 +7,15 @@ 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
usage.footerHeading=Powered by picocli%n usage.footerHeading=Powered by picocli%n

View file

@ -7,10 +7,13 @@ 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
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.footerHeading=Powered by Picocli%n usage.footerHeading=Powered by Picocli%n

View file

@ -9,10 +9,14 @@ locale=Locale for description texts
# Generic # Generic
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

@ -10,9 +10,13 @@ locale=Gebietsschema f
# Generic # Generic
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-added-capabilities=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-added-capabilities=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

@ -5,10 +5,13 @@ usage.header=Display version information about the tool
extended=Print an extended version string 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
sopv=Print the SOPV API version
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
usage.commandListHeading = %nCommands:%n 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

@ -5,10 +5,13 @@ usage.header=Zeige Versionsinformationen
extended=Gebe erweiterte Versionsinformationen aus 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
sopv=Gebe die SOPV API Version aus
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
usage.commandListHeading=%nBefehle:%n 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

@ -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

@ -8,9 +8,13 @@ import sop.Verification;
import sop.enums.SignatureMode; import sop.enums.SignatureMode;
import sop.testsuite.JUtils; import sop.testsuite.JUtils;
import java.text.ParseException;
import java.util.Date; import java.util.Date;
import java.util.function.Predicate;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class VerificationAssert { public final class VerificationAssert {
@ -45,18 +49,39 @@ public final class VerificationAssert {
} }
public VerificationAssert hasDescription(String description) { public VerificationAssert hasDescription(String description) {
assertEquals(description, verification.getDescription().get()); assertEquals(description, verification.getJsonOrDescription().get());
return this; return this;
} }
public VerificationAssert hasDescriptionOrNull(String description) { public VerificationAssert hasDescriptionOrNull(String description) {
if (verification.getDescription().isEmpty()) { if (verification.getJsonOrDescription().isEmpty()) {
return this; return this;
} }
return hasDescription(description); return hasDescription(description);
} }
public VerificationAssert hasValidJSONOrNull(Verification.JSONParser parser)
throws ParseException {
if (!verification.getJsonOrDescription().isPresent()) {
// missing description
return this;
}
return hasJSON(parser, null);
}
public VerificationAssert hasJSON(Verification.JSONParser parser, Predicate<Verification.JSON> predicate) {
assertTrue(verification.getContainsJson(), "Verification does not appear to contain JSON extension");
Verification.JSON json = verification.getJson(parser);
assertNotNull(verification.getJson(parser), "Verification does not appear to contain valid JSON extension.");
if (predicate != null) {
assertTrue(predicate.test(json), "JSON object does not match predicate.");
}
return this;
}
public VerificationAssert hasMode(SignatureMode mode) { public VerificationAssert hasMode(SignatureMode mode) {
assertEquals(mode, verification.getSignatureMode().get()); assertEquals(mode, verification.getSignatureMode().get());
return this; return this;

View file

@ -4,10 +4,13 @@
package sop.testsuite.operation; package sop.testsuite.operation;
import kotlin.jvm.functions.Function0;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import sop.SOP; import sop.SOP;
import sop.exception.SOPGPException;
import sop.testsuite.AbortOnUnsupportedOption; import sop.testsuite.AbortOnUnsupportedOption;
import sop.testsuite.AbortOnUnsupportedOptionExtension; import sop.testsuite.AbortOnUnsupportedOptionExtension;
import sop.testsuite.SOPInstanceFactory; import sop.testsuite.SOPInstanceFactory;
@ -18,6 +21,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@ExtendWith(AbortOnUnsupportedOptionExtension.class) @ExtendWith(AbortOnUnsupportedOptionExtension.class)
@AbortOnUnsupportedOption @AbortOnUnsupportedOption
public abstract class AbstractSOPTest { public abstract class AbstractSOPTest {
@ -51,6 +56,17 @@ public abstract class AbstractSOPTest {
} }
} }
public <T> T assumeSupported(Function0<T> f) {
try {
T t = f.invoke();
assumeTrue(t != null, "Unsupported operation.");
return t;
} catch (SOPGPException.UnsupportedSubcommand e) {
assumeTrue(false, e.getMessage());
return null;
}
}
public static Stream<Arguments> provideBackends() { public static Stream<Arguments> provideBackends() {
return backends.stream(); return backends.stream();
} }

View file

@ -20,7 +20,7 @@ import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class ArmorDearmorTest { public class ArmorDearmorTest extends AbstractSOPTest {
static Stream<Arguments> provideInstances() { static Stream<Arguments> provideInstances() {
return AbstractSOPTest.provideBackends(); return AbstractSOPTest.provideBackends();
@ -31,13 +31,13 @@ public class ArmorDearmorTest {
public void dearmorArmorAliceKey(SOP sop) throws IOException { public void dearmorArmorAliceKey(SOP sop) throws IOException {
byte[] aliceKey = TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8); byte[] aliceKey = TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8);
byte[] dearmored = sop.dearmor() byte[] dearmored = assumeSupported(sop::dearmor)
.data(aliceKey) .data(aliceKey)
.getBytes(); .getBytes();
Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK));
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(dearmored) .data(dearmored)
.getBytes(); .getBytes();
@ -52,13 +52,13 @@ public class ArmorDearmorTest {
public void dearmorArmorAliceCert(SOP sop) throws IOException { public void dearmorArmorAliceCert(SOP sop) throws IOException {
byte[] aliceCert = TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8); byte[] aliceCert = TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8);
byte[] dearmored = sop.dearmor() byte[] dearmored = assumeSupported(sop::dearmor)
.data(aliceCert) .data(aliceCert)
.getBytes(); .getBytes();
Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK));
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(dearmored) .data(dearmored)
.getBytes(); .getBytes();
@ -73,13 +73,13 @@ public class ArmorDearmorTest {
public void dearmorArmorBobKey(SOP sop) throws IOException { public void dearmorArmorBobKey(SOP sop) throws IOException {
byte[] bobKey = TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8); byte[] bobKey = TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8);
byte[] dearmored = sop.dearmor() byte[] dearmored = assumeSupported(sop::dearmor)
.data(bobKey) .data(bobKey)
.getBytes(); .getBytes();
Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK));
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(dearmored) .data(dearmored)
.getBytes(); .getBytes();
@ -94,13 +94,13 @@ public class ArmorDearmorTest {
public void dearmorArmorBobCert(SOP sop) throws IOException { public void dearmorArmorBobCert(SOP sop) throws IOException {
byte[] bobCert = TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8); byte[] bobCert = TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8);
byte[] dearmored = sop.dearmor() byte[] dearmored = assumeSupported(sop::dearmor)
.data(bobCert) .data(bobCert)
.getBytes(); .getBytes();
Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK));
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(dearmored) .data(dearmored)
.getBytes(); .getBytes();
@ -115,13 +115,13 @@ public class ArmorDearmorTest {
public void dearmorArmorCarolKey(SOP sop) throws IOException { public void dearmorArmorCarolKey(SOP sop) throws IOException {
byte[] carolKey = TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8); byte[] carolKey = TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8);
byte[] dearmored = sop.dearmor() byte[] dearmored = assumeSupported(sop::dearmor)
.data(carolKey) .data(carolKey)
.getBytes(); .getBytes();
Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK));
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(dearmored) .data(dearmored)
.getBytes(); .getBytes();
@ -136,13 +136,13 @@ public class ArmorDearmorTest {
public void dearmorArmorCarolCert(SOP sop) throws IOException { public void dearmorArmorCarolCert(SOP sop) throws IOException {
byte[] carolCert = TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8); byte[] carolCert = TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8);
byte[] dearmored = sop.dearmor() byte[] dearmored = assumeSupported(sop::dearmor)
.data(carolCert) .data(carolCert)
.getBytes(); .getBytes();
Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK));
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(dearmored) .data(dearmored)
.getBytes(); .getBytes();
@ -163,13 +163,13 @@ public class ArmorDearmorTest {
"CePQFpprprnGEzpE3flQLUc=\n" + "CePQFpprprnGEzpE3flQLUc=\n" +
"=ZiFR\n" + "=ZiFR\n" +
"-----END PGP MESSAGE-----\n").getBytes(StandardCharsets.UTF_8); "-----END PGP MESSAGE-----\n").getBytes(StandardCharsets.UTF_8);
byte[] dearmored = sop.dearmor() byte[] dearmored = assumeSupported(sop::dearmor)
.data(message) .data(message)
.getBytes(); .getBytes();
Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_MESSAGE)); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_MESSAGE));
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(dearmored) .data(dearmored)
.getBytes(); .getBytes();
@ -191,13 +191,13 @@ public class ArmorDearmorTest {
"=GHvQ\n" + "=GHvQ\n" +
"-----END PGP SIGNATURE-----\n").getBytes(StandardCharsets.UTF_8); "-----END PGP SIGNATURE-----\n").getBytes(StandardCharsets.UTF_8);
byte[] dearmored = sop.dearmor() byte[] dearmored = assumeSupported(sop::dearmor)
.data(signature) .data(signature)
.getBytes(); .getBytes();
Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_SIGNATURE)); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_SIGNATURE));
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(dearmored) .data(dearmored)
.getBytes(); .getBytes();
@ -210,11 +210,11 @@ public class ArmorDearmorTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void testDearmoringTwiceIsIdempotent(SOP sop) throws IOException { public void testDearmoringTwiceIsIdempotent(SOP sop) throws IOException {
byte[] dearmored = sop.dearmor() byte[] dearmored = assumeSupported(sop::dearmor)
.data(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.getBytes(); .getBytes();
byte[] dearmoredAgain = sop.dearmor() byte[] dearmoredAgain = assumeSupported(sop::dearmor)
.data(dearmored) .data(dearmored)
.getBytes(); .getBytes();
@ -233,7 +233,7 @@ public class ArmorDearmorTest {
"=GHvQ\n" + "=GHvQ\n" +
"-----END PGP SIGNATURE-----\n").getBytes(StandardCharsets.UTF_8); "-----END PGP SIGNATURE-----\n").getBytes(StandardCharsets.UTF_8);
byte[] armoredAgain = sop.armor() byte[] armoredAgain = assumeSupported(sop::armor)
.data(armored) .data(armored)
.getBytes(); .getBytes();

View file

@ -0,0 +1,193 @@
// 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.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class CertifyValidateUserIdTest extends AbstractSOPTest {
static Stream<Arguments> provideInstances() {
return AbstractSOPTest.provideBackends();
}
@ParameterizedTest
@MethodSource("provideInstances")
public void certifyUserId(SOP sop) throws IOException {
byte[] aliceKey = assumeSupported(sop::generateKey)
.withKeyPassword("sw0rdf1sh")
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] aliceCert = assumeSupported(sop::extractCert)
.key(aliceKey)
.getBytes();
byte[] bobKey = assumeSupported(sop::generateKey)
.userId("Bob <bob@pgpainless.org>")
.generate()
.getBytes();
byte[] bobCert = assumeSupported(sop::extractCert)
.key(bobKey)
.getBytes();
// Alice has her own user-id self-certified
assertTrue(assumeSupported(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
assertThrows(SOPGPException.CertUserIdNoMatch.class, () ->
assumeSupported(sop::validateUserId)
.authorities(aliceCert)
.userId("Bob <bob@pgpainless.org>")
.subjects(bobCert),
"Alice has not yet certified Bobs user-id");
byte[] bobCertifiedByAlice = assumeSupported(sop::certifyUserId)
.userId("Bob <bob@pgpainless.org>")
.withKeyPassword("sw0rdf1sh")
.keys(aliceKey)
.certs(bobCert)
.getBytes();
assertTrue(assumeSupported(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 = assumeSupported(sop::generateKey)
.noArmor()
.withKeyPassword("sw0rdf1sh")
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] aliceCert = assumeSupported(sop::extractCert)
.noArmor()
.key(aliceKey)
.getBytes();
byte[] bobKey = assumeSupported(sop::generateKey)
.noArmor()
.userId("Bob <bob@pgpainless.org>")
.generate()
.getBytes();
byte[] bobCert = assumeSupported(sop::extractCert)
.noArmor()
.key(bobKey)
.getBytes();
byte[] bobCertifiedByAlice = assumeSupported(sop::certifyUserId)
.noArmor()
.userId("Bob <bob@pgpainless.org>")
.withKeyPassword("sw0rdf1sh")
.keys(aliceKey)
.certs(bobCert)
.getBytes();
assertTrue(assumeSupported(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 = assumeSupported(sop::generateKey)
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] aliceCert = assumeSupported(sop::extractCert)
.key(aliceKey)
.getBytes();
byte[] bobKey = assumeSupported(sop::generateKey)
.userId("Bob <bob@pgpainless.org>")
.generate()
.getBytes();
byte[] bobCert = assumeSupported(sop::extractCert)
.key(bobKey)
.getBytes();
assertThrows(SOPGPException.CertUserIdNoMatch.class, () ->
assumeSupported(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 = assumeSupported(sop::certifyUserId)
.userId("Bobby")
.noRequireSelfSig()
.keys(aliceKey)
.certs(bobCert)
.getBytes();
assertTrue(assumeSupported(sop::validateUserId)
.userId("Bobby")
.authorities(aliceCert)
.subjects(bobWithPetName),
"Alice accepts the pet-name she gave to Bob");
assertThrows(SOPGPException.CertUserIdNoMatch.class, () ->
assumeSupported(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 = assumeSupported(sop::generateKey)
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] aliceRevokedCert = assumeSupported(sop::revokeKey)
.keys(aliceKey)
.getBytes();
byte[] aliceRevokedKey = assumeSupported(sop::updateKey)
.mergeCerts(aliceRevokedCert)
.key(aliceKey)
.getBytes();
byte[] bobKey = assumeSupported(sop::generateKey)
.userId("Bob <bob@pgpainless.org>")
.generate()
.getBytes();
byte[] bobCert = assumeSupported(sop::extractCert)
.key(bobKey)
.getBytes();
assertThrows(SOPGPException.KeyCannotCertify.class, () ->
assumeSupported(sop::certifyUserId)
.userId("Bob <bob@pgpainless.org>")
.keys(aliceRevokedKey)
.certs(bobCert)
.getBytes());
}
}

View file

@ -32,18 +32,18 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void changePasswordFromUnprotectedToProtected(SOP sop) throws IOException { public void changePasswordFromUnprotectedToProtected(SOP sop) throws IOException {
byte[] unprotectedKey = sop.generateKey().generate().getBytes(); byte[] unprotectedKey = assumeSupported(sop::generateKey).generate().getBytes();
byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8);
byte[] protectedKey = sop.changeKeyPassword().newKeyPassphrase(password).keys(unprotectedKey).getBytes(); byte[] protectedKey = assumeSupported(sop::changeKeyPassword).newKeyPassphrase(password).keys(unprotectedKey).getBytes();
sop.sign().withKeyPassword(password).key(protectedKey).data("Test123".getBytes(StandardCharsets.UTF_8)); assumeSupported(sop::sign).withKeyPassword(password).key(protectedKey).data("Test123".getBytes(StandardCharsets.UTF_8));
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void changePasswordFromUnprotectedToUnprotected(SOP sop) throws IOException { public void changePasswordFromUnprotectedToUnprotected(SOP sop) throws IOException {
byte[] unprotectedKey = sop.generateKey().noArmor().generate().getBytes(); byte[] unprotectedKey = assumeSupported(sop::generateKey).noArmor().generate().getBytes();
byte[] stillUnprotectedKey = sop.changeKeyPassword().noArmor().keys(unprotectedKey).getBytes(); byte[] stillUnprotectedKey = assumeSupported(sop::changeKeyPassword).noArmor().keys(unprotectedKey).getBytes();
assertArrayEquals(unprotectedKey, stillUnprotectedKey); assertArrayEquals(unprotectedKey, stillUnprotectedKey);
} }
@ -52,12 +52,12 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void changePasswordFromProtectedToUnprotected(SOP sop) throws IOException { public void changePasswordFromProtectedToUnprotected(SOP sop) throws IOException {
byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8);
byte[] protectedKey = sop.generateKey().withKeyPassword(password).generate().getBytes(); byte[] protectedKey = assumeSupported(sop::generateKey).withKeyPassword(password).generate().getBytes();
byte[] unprotectedKey = sop.changeKeyPassword() byte[] unprotectedKey = assumeSupported(sop::changeKeyPassword)
.oldKeyPassphrase(password) .oldKeyPassphrase(password)
.keys(protectedKey).getBytes(); .keys(protectedKey).getBytes();
sop.sign().key(unprotectedKey).data("Test123".getBytes(StandardCharsets.UTF_8)); assumeSupported(sop::sign).key(unprotectedKey).data("Test123".getBytes(StandardCharsets.UTF_8));
} }
@ParameterizedTest @ParameterizedTest
@ -65,13 +65,13 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest {
public void changePasswordFromProtectedToDifferentProtected(SOP sop) throws IOException { public void changePasswordFromProtectedToDifferentProtected(SOP sop) throws IOException {
byte[] oldPassword = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] oldPassword = "sw0rdf1sh".getBytes(UTF8Util.UTF8);
byte[] newPassword = "0r4ng3".getBytes(UTF8Util.UTF8); byte[] newPassword = "0r4ng3".getBytes(UTF8Util.UTF8);
byte[] protectedKey = sop.generateKey().withKeyPassword(oldPassword).generate().getBytes(); byte[] protectedKey = assumeSupported(sop::generateKey).withKeyPassword(oldPassword).generate().getBytes();
byte[] reprotectedKey = sop.changeKeyPassword() byte[] reprotectedKey = assumeSupported(sop::changeKeyPassword)
.oldKeyPassphrase(oldPassword) .oldKeyPassphrase(oldPassword)
.newKeyPassphrase(newPassword) .newKeyPassphrase(newPassword)
.keys(protectedKey).getBytes(); .keys(protectedKey).getBytes();
sop.sign().key(reprotectedKey).withKeyPassword(newPassword).data("Test123".getBytes(StandardCharsets.UTF_8)); assumeSupported(sop::sign).key(reprotectedKey).withKeyPassword(newPassword).data("Test123".getBytes(StandardCharsets.UTF_8));
} }
@ -82,8 +82,8 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest {
byte[] newPassword = "monkey123".getBytes(UTF8Util.UTF8); byte[] newPassword = "monkey123".getBytes(UTF8Util.UTF8);
byte[] wrongPassword = "0r4ng3".getBytes(UTF8Util.UTF8); byte[] wrongPassword = "0r4ng3".getBytes(UTF8Util.UTF8);
byte[] protectedKey = sop.generateKey().withKeyPassword(oldPassword).generate().getBytes(); byte[] protectedKey = assumeSupported(sop::generateKey).withKeyPassword(oldPassword).generate().getBytes();
assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.changeKeyPassword() assertThrows(SOPGPException.KeyIsProtected.class, () -> assumeSupported(sop::changeKeyPassword)
.oldKeyPassphrase(wrongPassword) .oldKeyPassphrase(wrongPassword)
.newKeyPassphrase(newPassword) .newKeyPassphrase(newPassword)
.keys(protectedKey).getBytes()); .keys(protectedKey).getBytes());
@ -93,9 +93,9 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void nonUtf8PasswordsFail(SOP sop) { public void nonUtf8PasswordsFail(SOP sop) {
assertThrows(SOPGPException.PasswordNotHumanReadable.class, () -> assertThrows(SOPGPException.PasswordNotHumanReadable.class, () ->
sop.changeKeyPassword().oldKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe})); assumeSupported(sop::changeKeyPassword).oldKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe}));
assertThrows(SOPGPException.PasswordNotHumanReadable.class, () -> assertThrows(SOPGPException.PasswordNotHumanReadable.class, () ->
sop.changeKeyPassword().newKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe})); assumeSupported(sop::changeKeyPassword).newKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe}));
} }
@ -104,16 +104,16 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest {
public void testNoArmor(SOP sop) throws IOException { public void testNoArmor(SOP sop) throws IOException {
byte[] oldPassword = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] oldPassword = "sw0rdf1sh".getBytes(UTF8Util.UTF8);
byte[] newPassword = "0r4ng3".getBytes(UTF8Util.UTF8); byte[] newPassword = "0r4ng3".getBytes(UTF8Util.UTF8);
byte[] protectedKey = sop.generateKey().withKeyPassword(oldPassword).generate().getBytes(); byte[] protectedKey = assumeSupported(sop::generateKey).withKeyPassword(oldPassword).generate().getBytes();
byte[] armored = sop.changeKeyPassword() byte[] armored = assumeSupported(sop::changeKeyPassword)
.oldKeyPassphrase(oldPassword) .oldKeyPassphrase(oldPassword)
.newKeyPassphrase(newPassword) .newKeyPassphrase(newPassword)
.keys(protectedKey) .keys(protectedKey)
.getBytes(); .getBytes();
JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK);
byte[] unarmored = sop.changeKeyPassword() byte[] unarmored = assumeSupported(sop::changeKeyPassword)
.noArmor() .noArmor()
.oldKeyPassphrase(oldPassword) .oldKeyPassphrase(oldPassword)
.newKeyPassphrase(newPassword) .newKeyPassphrase(newPassword)

View file

@ -41,7 +41,7 @@ public class DecryptWithSessionKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void testDecryptAndExtractSessionKey(SOP sop) throws IOException { public void testDecryptAndExtractSessionKey(SOP sop) throws IOException {
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt() ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
.withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8))
.toByteArrayAndResult(); .toByteArrayAndResult();
@ -54,7 +54,7 @@ public class DecryptWithSessionKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void testDecryptWithSessionKey(SOP sop) throws IOException { public void testDecryptWithSessionKey(SOP sop) throws IOException {
byte[] decrypted = sop.decrypt() byte[] decrypted = assumeSupported(sop::decrypt)
.withSessionKey(SessionKey.fromString(SESSION_KEY)) .withSessionKey(SessionKey.fromString(SESSION_KEY))
.ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8))
.toByteArrayAndResult() .toByteArrayAndResult()

View file

@ -37,13 +37,13 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
public void signVerifyWithAliceKey(SOP sop) throws IOException { public void signVerifyWithAliceKey(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signature = sop.detachedSign() byte[] signature = assumeSupported(sop::detachedSign)
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
List<Verification> verificationList = sop.detachedVerify() List<Verification> verificationList = assumeSupported(sop::detachedVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(signature) .signatures(signature)
.data(message); .data(message);
@ -60,14 +60,14 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
public void signVerifyTextModeWithAliceKey(SOP sop) throws IOException { public void signVerifyTextModeWithAliceKey(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signature = sop.detachedSign() byte[] signature = assumeSupported(sop::detachedSign)
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.mode(SignAs.text) .mode(SignAs.text)
.data(message) .data(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
List<Verification> verificationList = sop.detachedVerify() List<Verification> verificationList = assumeSupported(sop::detachedVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(signature) .signatures(signature)
.data(message); .data(message);
@ -85,7 +85,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8);
List<Verification> verificationList = sop.detachedVerify() List<Verification> verificationList = assumeSupported(sop::detachedVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(signature) .signatures(signature)
.data(message); .data(message);
@ -101,13 +101,13 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
public void signVerifyWithBobKey(SOP sop) throws IOException { public void signVerifyWithBobKey(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signature = sop.detachedSign() byte[] signature = assumeSupported(sop::detachedSign)
.key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
List<Verification> verificationList = sop.detachedVerify() List<Verification> verificationList = assumeSupported(sop::detachedVerify)
.cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(signature) .signatures(signature)
.data(message); .data(message);
@ -123,13 +123,13 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
public void signVerifyWithCarolKey(SOP sop) throws IOException { public void signVerifyWithCarolKey(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signature = sop.detachedSign() byte[] signature = assumeSupported(sop::detachedSign)
.key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
List<Verification> verificationList = sop.detachedVerify() List<Verification> verificationList = assumeSupported(sop::detachedVerify)
.cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(signature) .signatures(signature)
.data(message); .data(message);
@ -145,7 +145,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
public void signVerifyWithEncryptedKey(SOP sop) throws IOException { public void signVerifyWithEncryptedKey(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signature = sop.detachedSign() byte[] signature = assumeSupported(sop::detachedSign)
.key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8))
.withKeyPassword(TestData.PASSWORD) .withKeyPassword(TestData.PASSWORD)
.data(message) .data(message)
@ -154,7 +154,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
JUtils.assertArrayStartsWith(signature, TestData.BEGIN_PGP_SIGNATURE); JUtils.assertArrayStartsWith(signature, TestData.BEGIN_PGP_SIGNATURE);
List<Verification> verificationList = sop.detachedVerify() List<Verification> verificationList = assumeSupported(sop::detachedVerify)
.cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(signature) .signatures(signature)
.data(message); .data(message);
@ -170,18 +170,18 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
public void signArmorVerifyWithBobKey(SOP sop) throws IOException { public void signArmorVerifyWithBobKey(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signature = sop.detachedSign() byte[] signature = assumeSupported(sop::detachedSign)
.key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8))
.noArmor() .noArmor()
.data(message) .data(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(signature) .data(signature)
.getBytes(); .getBytes();
List<Verification> verificationList = sop.detachedVerify() List<Verification> verificationList = assumeSupported(sop::detachedVerify)
.cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(armored) .signatures(armored)
.data(message); .data(message);
@ -199,7 +199,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8);
Date beforeSignature = new Date(TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() - 1000); // 1 sec before sig Date beforeSignature = new Date(TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() - 1000); // 1 sec before sig
assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::detachedVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.notAfter(beforeSignature) .notAfter(beforeSignature)
.signatures(signature) .signatures(signature)
@ -213,7 +213,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8);
Date afterSignature = new Date(TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() + 1000); // 1 sec after sig Date afterSignature = new Date(TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() + 1000); // 1 sec after sig
assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::detachedVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.notBefore(afterSignature) .notBefore(afterSignature)
.signatures(signature) .signatures(signature)
@ -224,13 +224,13 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void signWithAliceVerifyWithBobThrowsNoSignature(SOP sop) throws IOException { public void signWithAliceVerifyWithBobThrowsNoSignature(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signatures = sop.detachedSign() byte[] signatures = assumeSupported(sop::detachedSign)
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::detachedVerify)
.cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(signatures) .signatures(signatures)
.data(message)); .data(message));
@ -240,7 +240,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void signVerifyWithEncryptedKeyWithoutPassphraseFails(SOP sop) { public void signVerifyWithEncryptedKeyWithoutPassphraseFails(SOP sop) {
assertThrows(SOPGPException.KeyIsProtected.class, () -> assertThrows(SOPGPException.KeyIsProtected.class, () ->
sop.detachedSign() assumeSupported(sop::detachedSign)
.key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8))
.data(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) .data(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8))
.toByteArrayAndResult() .toByteArrayAndResult()
@ -253,7 +253,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
throws IOException { throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signature = sop.sign() byte[] signature = assumeSupported(sop::sign)
.key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8))
.withKeyPassword("wrong") .withKeyPassword("wrong")
.withKeyPassword(TestData.PASSWORD) // correct .withKeyPassword(TestData.PASSWORD) // correct
@ -262,7 +262,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
List<Verification> verificationList = sop.verify() List<Verification> verificationList = assumeSupported(sop::verify)
.cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(signature) .signatures(signature)
.data(message); .data(message);
@ -279,7 +279,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
assertThrows(SOPGPException.MissingArg.class, () -> assertThrows(SOPGPException.MissingArg.class, () ->
sop.verify() assumeSupported(sop::verify)
.signatures(TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)) .signatures(TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8))
.data(message)); .data(message));
} }
@ -288,14 +288,14 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void signVerifyWithMultipleKeys(SOP sop) throws IOException { public void signVerifyWithMultipleKeys(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signatures = sop.detachedSign() byte[] signatures = assumeSupported(sop::detachedSign)
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
List<Verification> verificationList = sop.detachedVerify() List<Verification> verificationList = assumeSupported(sop::detachedVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(signatures) .signatures(signatures)

View file

@ -11,12 +11,15 @@ import org.junit.jupiter.params.provider.MethodSource;
import sop.ByteArrayAndResult; import sop.ByteArrayAndResult;
import sop.DecryptionResult; import sop.DecryptionResult;
import sop.EncryptionResult; import sop.EncryptionResult;
import sop.Profile;
import sop.SOP; import sop.SOP;
import sop.SessionKey; import sop.SessionKey;
import sop.Verification; import sop.Verification;
import sop.enums.EncryptAs; import sop.enums.EncryptAs;
import sop.enums.SignatureMode; import sop.enums.SignatureMode;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
import sop.operation.Decrypt;
import sop.operation.Encrypt;
import sop.testsuite.TestData; import sop.testsuite.TestData;
import sop.testsuite.assertions.VerificationListAssert; import sop.testsuite.assertions.VerificationListAssert;
import sop.util.Optional; import sop.util.Optional;
@ -25,6 +28,7 @@ import sop.util.UTCUtil;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -45,7 +49,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void encryptDecryptRoundTripPasswordTest(SOP sop) throws IOException { public void encryptDecryptRoundTripPasswordTest(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
ByteArrayAndResult<EncryptionResult> encResult = sop.encrypt() ByteArrayAndResult<EncryptionResult> encResult = assumeSupported(sop::encrypt)
.withPassword("sw0rdf1sh") .withPassword("sw0rdf1sh")
.plaintext(message) .plaintext(message)
.toByteArrayAndResult(); .toByteArrayAndResult();
@ -53,7 +57,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
byte[] ciphertext = encResult.getBytes(); byte[] ciphertext = encResult.getBytes();
Optional<SessionKey> encSessionKey = encResult.getResult().getSessionKey(); Optional<SessionKey> encSessionKey = encResult.getResult().getSessionKey();
ByteArrayAndResult<DecryptionResult> decResult = sop.decrypt() ByteArrayAndResult<DecryptionResult> decResult = assumeSupported(sop::decrypt)
.withPassword("sw0rdf1sh") .withPassword("sw0rdf1sh")
.ciphertext(ciphertext) .ciphertext(ciphertext)
.toByteArrayAndResult(); .toByteArrayAndResult();
@ -61,9 +65,10 @@ public class EncryptDecryptTest extends AbstractSOPTest {
byte[] plaintext = decResult.getBytes(); byte[] plaintext = decResult.getBytes();
Optional<SessionKey> decSessionKey = decResult.getResult().getSessionKey(); Optional<SessionKey> decSessionKey = decResult.getResult().getSessionKey();
assertArrayEquals(message, plaintext); assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
if (encSessionKey.isPresent() && decSessionKey.isPresent()) { if (encSessionKey.isPresent() && decSessionKey.isPresent()) {
assertEquals(encSessionKey.get(), decSessionKey.get()); assertEquals(encSessionKey.get(), decSessionKey.get(),
"Extracted Session Key mismatch.");
} }
} }
@ -71,91 +76,93 @@ public class EncryptDecryptTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void encryptDecryptRoundTripAliceTest(SOP sop) throws IOException { public void encryptDecryptRoundTripAliceTest(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = sop.encrypt() byte[] ciphertext = assumeSupported(sop::encrypt)
.withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.plaintext(message) .plaintext(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt() ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
.withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.ciphertext(ciphertext) .ciphertext(ciphertext)
.toByteArrayAndResult(); .toByteArrayAndResult();
byte[] plaintext = bytesAndResult.getBytes(); byte[] plaintext = bytesAndResult.getBytes();
assertArrayEquals(message, plaintext); assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
DecryptionResult result = bytesAndResult.getResult(); DecryptionResult result = bytesAndResult.getResult();
assertNotNull(result.getSessionKey().get()); if (result.getSessionKey().isPresent()) {
assertNotNull(result.getSessionKey().get(), "Session key MUST NOT be null.");
}
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void encryptDecryptRoundTripBobTest(SOP sop) throws IOException { public void encryptDecryptRoundTripBobTest(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = sop.encrypt() byte[] ciphertext = assumeSupported(sop::encrypt)
.withCert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .withCert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8))
.plaintext(message) .plaintext(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
byte[] plaintext = sop.decrypt() byte[] plaintext = assumeSupported(sop::decrypt)
.withKey(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .withKey(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8))
.ciphertext(ciphertext) .ciphertext(ciphertext)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
assertArrayEquals(message, plaintext); assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void encryptDecryptRoundTripCarolTest(SOP sop) throws IOException { public void encryptDecryptRoundTripCarolTest(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = sop.encrypt() byte[] ciphertext = assumeSupported(sop::encrypt)
.withCert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .withCert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8))
.plaintext(message) .plaintext(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
byte[] plaintext = sop.decrypt() byte[] plaintext = assumeSupported(sop::decrypt)
.withKey(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .withKey(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8))
.ciphertext(ciphertext) .ciphertext(ciphertext)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
assertArrayEquals(message, plaintext); assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void encryptNoArmorThenArmorThenDecryptRoundTrip(SOP sop) throws IOException { public void encryptNoArmorThenArmorThenDecryptRoundTrip(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = sop.encrypt() byte[] ciphertext = assumeSupported(sop::encrypt)
.withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.noArmor() .noArmor()
.plaintext(message) .plaintext(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(ciphertext) .data(ciphertext)
.getBytes(); .getBytes();
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt() ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
.withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.ciphertext(armored) .ciphertext(armored)
.toByteArrayAndResult(); .toByteArrayAndResult();
byte[] plaintext = bytesAndResult.getBytes(); byte[] plaintext = bytesAndResult.getBytes();
assertArrayEquals(message, plaintext); assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void encryptSignDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException { public void encryptSignDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = sop.encrypt() byte[] ciphertext = assumeSupported(sop::encrypt)
.withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.mode(EncryptAs.binary) .mode(EncryptAs.binary)
@ -163,17 +170,19 @@ public class EncryptDecryptTest extends AbstractSOPTest {
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt() ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
.withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.ciphertext(ciphertext) .ciphertext(ciphertext)
.toByteArrayAndResult(); .toByteArrayAndResult();
byte[] plaintext = bytesAndResult.getBytes(); byte[] plaintext = bytesAndResult.getBytes();
assertArrayEquals(message, plaintext); assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
DecryptionResult result = bytesAndResult.getResult(); DecryptionResult result = bytesAndResult.getResult();
assertNotNull(result.getSessionKey().get()); if (result.getSessionKey().isPresent()) {
assertNotNull(result.getSessionKey().get(), "Session key MUST NOT be null.");
}
List<Verification> verificationList = result.getVerifications(); List<Verification> verificationList = result.getVerifications();
VerificationListAssert.assertThatVerificationList(verificationList) VerificationListAssert.assertThatVerificationList(verificationList)
@ -187,7 +196,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void encryptSignAsTextDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException { public void encryptSignAsTextDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = sop.encrypt() byte[] ciphertext = assumeSupported(sop::encrypt)
.withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.mode(EncryptAs.text) .mode(EncryptAs.text)
@ -195,14 +204,14 @@ public class EncryptDecryptTest extends AbstractSOPTest {
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt() ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
.withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.ciphertext(ciphertext) .ciphertext(ciphertext)
.toByteArrayAndResult(); .toByteArrayAndResult();
byte[] plaintext = bytesAndResult.getBytes(); byte[] plaintext = bytesAndResult.getBytes();
assertArrayEquals(message, plaintext); assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
DecryptionResult result = bytesAndResult.getResult(); DecryptionResult result = bytesAndResult.getResult();
assertNotNull(result.getSessionKey().get()); assertNotNull(result.getSessionKey().get());
@ -218,17 +227,17 @@ public class EncryptDecryptTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void encryptSignDecryptVerifyRoundTripWithFreshEncryptedKeyTest(SOP sop) throws IOException { public void encryptSignDecryptVerifyRoundTripWithFreshEncryptedKeyTest(SOP sop) throws IOException {
byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8);
byte[] key = sop.generateKey() byte[] key = assumeSupported(sop::generateKey)
.withKeyPassword(keyPassword) .withKeyPassword(keyPassword)
.userId("Alice <alice@openpgp.org>") .userId("Alice <alice@openpgp.org>")
.generate() .generate()
.getBytes(); .getBytes();
byte[] cert = sop.extractCert() byte[] cert = assumeSupported(sop::extractCert)
.key(key) .key(key)
.getBytes(); .getBytes();
byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = sop.encrypt() byte[] ciphertext = assumeSupported(sop::encrypt)
.withCert(cert) .withCert(cert)
.signWith(key) .signWith(key)
.withKeyPassword(keyPassword) .withKeyPassword(keyPassword)
@ -236,7 +245,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes(); .getBytes();
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt() ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
.withKey(key) .withKey(key)
.withKeyPassword(keyPassword) .withKeyPassword(keyPassword)
.verifyWithCert(cert) .verifyWithCert(cert)
@ -269,7 +278,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before signing date Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before signing date
assertThrows(SOPGPException.NoSignature.class, () -> { assertThrows(SOPGPException.NoSignature.class, () -> {
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt() ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
.withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.verifyNotAfter(beforeSignature) .verifyNotAfter(beforeSignature)
@ -303,7 +312,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec after signing date Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec after signing date
assertThrows(SOPGPException.NoSignature.class, () -> { assertThrows(SOPGPException.NoSignature.class, () -> {
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt() ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
.withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.verifyNotBefore(afterSignature) .verifyNotBefore(afterSignature)
@ -322,7 +331,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
public void missingArgsTest(SOP sop) { public void missingArgsTest(SOP sop) {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
assertThrows(SOPGPException.MissingArg.class, () -> sop.encrypt() assertThrows(SOPGPException.MissingArg.class, () -> assumeSupported(sop::encrypt)
.plaintext(message) .plaintext(message)
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes()); .getBytes());
@ -332,10 +341,61 @@ public class EncryptDecryptTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void passingSecretKeysForPublicKeysFails(SOP sop) { public void passingSecretKeysForPublicKeysFails(SOP sop) {
assertThrows(SOPGPException.BadData.class, () -> assertThrows(SOPGPException.BadData.class, () ->
sop.encrypt() assumeSupported(sop::encrypt)
.withCert(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .withCert(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) .plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8))
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes()); .getBytes());
} }
@ParameterizedTest
@MethodSource("provideInstances")
public void encryptDecryptWithAllSupportedKeyGenerationProfiles(SOP sop) throws IOException {
List<Profile> profiles = assumeSupported(sop::listProfiles).generateKey();
List<byte[]> keys = new ArrayList<>();
List<byte[]> certs = new ArrayList<>();
for (Profile p : profiles) {
byte[] k = assumeSupported(sop::generateKey)
.profile(p)
.userId(p.getName())
.generate()
.getBytes();
keys.add(k);
byte[] c = assumeSupported(sop::extractCert)
.key(k)
.getBytes();
certs.add(c);
}
byte[] plaintext = "Hello, World!\n".getBytes();
Encrypt encrypt = assumeSupported(sop::encrypt);
for (byte[] c : certs) {
encrypt.withCert(c);
}
for (byte[] k : keys) {
encrypt.signWith(k);
}
ByteArrayAndResult<EncryptionResult> encRes = encrypt.plaintext(plaintext)
.toByteArrayAndResult();
EncryptionResult eResult = encRes.getResult();
byte[] ciphertext = encRes.getBytes();
for (byte[] k : keys) {
Decrypt decrypt = assumeSupported(sop::decrypt)
.withKey(k);
for (byte[] c : certs) {
decrypt.verifyWithCert(c);
}
ByteArrayAndResult<DecryptionResult> decRes = decrypt.ciphertext(ciphertext)
.toByteArrayAndResult();
DecryptionResult dResult = decRes.getResult();
byte[] decPlaintext = decRes.getBytes();
assertArrayEquals(plaintext, decPlaintext, "Decrypted plaintext does not match original plaintext.");
assertEquals(certs.size(), dResult.getVerifications().size());
}
}
} }

View file

@ -28,12 +28,12 @@ public class ExtractCertTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void extractArmoredCertFromArmoredKeyTest(SOP sop) throws IOException { public void extractArmoredCertFromArmoredKeyTest(SOP sop) throws IOException {
InputStream keyIn = sop.generateKey() InputStream keyIn = assumeSupported(sop::generateKey)
.userId("Alice <alice@openpgp.org>") .userId("Alice <alice@openpgp.org>")
.generate() .generate()
.getInputStream(); .getInputStream();
byte[] cert = sop.extractCert().key(keyIn).getBytes(); byte[] cert = assumeSupported(sop::extractCert).key(keyIn).getBytes();
JUtils.assertArrayStartsWith(cert, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK); JUtils.assertArrayStartsWith(cert, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK);
JUtils.assertArrayEndsWithIgnoreNewlines(cert, TestData.END_PGP_PUBLIC_KEY_BLOCK); JUtils.assertArrayEndsWithIgnoreNewlines(cert, TestData.END_PGP_PUBLIC_KEY_BLOCK);
} }
@ -41,7 +41,7 @@ public class ExtractCertTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void extractAliceCertFromAliceKeyTest(SOP sop) throws IOException { public void extractAliceCertFromAliceKeyTest(SOP sop) throws IOException {
byte[] armoredCert = sop.extractCert() byte[] armoredCert = assumeSupported(sop::extractCert)
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.getBytes(); .getBytes();
JUtils.assertAsciiArmorEquals(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); JUtils.assertAsciiArmorEquals(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8), armoredCert);
@ -50,7 +50,7 @@ public class ExtractCertTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void extractBobsCertFromBobsKeyTest(SOP sop) throws IOException { public void extractBobsCertFromBobsKeyTest(SOP sop) throws IOException {
byte[] armoredCert = sop.extractCert() byte[] armoredCert = assumeSupported(sop::extractCert)
.key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8))
.getBytes(); .getBytes();
JUtils.assertAsciiArmorEquals(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); JUtils.assertAsciiArmorEquals(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8), armoredCert);
@ -59,7 +59,7 @@ public class ExtractCertTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void extractCarolsCertFromCarolsKeyTest(SOP sop) throws IOException { public void extractCarolsCertFromCarolsKeyTest(SOP sop) throws IOException {
byte[] armoredCert = sop.extractCert() byte[] armoredCert = assumeSupported(sop::extractCert)
.key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8))
.getBytes(); .getBytes();
JUtils.assertAsciiArmorEquals(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); JUtils.assertAsciiArmorEquals(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8), armoredCert);
@ -68,12 +68,12 @@ public class ExtractCertTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void extractUnarmoredCertFromArmoredKeyTest(SOP sop) throws IOException { public void extractUnarmoredCertFromArmoredKeyTest(SOP sop) throws IOException {
InputStream keyIn = sop.generateKey() InputStream keyIn = assumeSupported(sop::generateKey)
.userId("Alice <alice@openpgp.org>") .userId("Alice <alice@openpgp.org>")
.generate() .generate()
.getInputStream(); .getInputStream();
byte[] cert = sop.extractCert() byte[] cert = assumeSupported(sop::extractCert)
.noArmor() .noArmor()
.key(keyIn) .key(keyIn)
.getBytes(); .getBytes();
@ -84,13 +84,13 @@ public class ExtractCertTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void extractArmoredCertFromUnarmoredKeyTest(SOP sop) throws IOException { public void extractArmoredCertFromUnarmoredKeyTest(SOP sop) throws IOException {
InputStream keyIn = sop.generateKey() InputStream keyIn = assumeSupported(sop::generateKey)
.userId("Alice <alice@openpgp.org>") .userId("Alice <alice@openpgp.org>")
.noArmor() .noArmor()
.generate() .generate()
.getInputStream(); .getInputStream();
byte[] cert = sop.extractCert() byte[] cert = assumeSupported(sop::extractCert)
.key(keyIn) .key(keyIn)
.getBytes(); .getBytes();
@ -101,13 +101,13 @@ public class ExtractCertTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void extractUnarmoredCertFromUnarmoredKeyTest(SOP sop) throws IOException { public void extractUnarmoredCertFromUnarmoredKeyTest(SOP sop) throws IOException {
InputStream keyIn = sop.generateKey() InputStream keyIn = assumeSupported(sop::generateKey)
.noArmor() .noArmor()
.userId("Alice <alice@openpgp.org>") .userId("Alice <alice@openpgp.org>")
.generate() .generate()
.getInputStream(); .getInputStream();
byte[] cert = sop.extractCert() byte[] cert = assumeSupported(sop::extractCert)
.noArmor() .noArmor()
.key(keyIn) .key(keyIn)
.getBytes(); .getBytes();

View file

@ -9,6 +9,7 @@ import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import sop.Profile;
import sop.SOP; import sop.SOP;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
import sop.testsuite.JUtils; import sop.testsuite.JUtils;
@ -16,9 +17,11 @@ import sop.testsuite.TestData;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class GenerateKeyTest extends AbstractSOPTest { public class GenerateKeyTest extends AbstractSOPTest {
@ -30,7 +33,7 @@ public class GenerateKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void generateKeyTest(SOP sop) throws IOException { public void generateKeyTest(SOP sop) throws IOException {
byte[] key = sop.generateKey() byte[] key = assumeSupported(sop::generateKey)
.userId("Alice <alice@openpgp.org>") .userId("Alice <alice@openpgp.org>")
.generate() .generate()
.getBytes(); .getBytes();
@ -42,7 +45,7 @@ public class GenerateKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void generateKeyNoArmor(SOP sop) throws IOException { public void generateKeyNoArmor(SOP sop) throws IOException {
byte[] key = sop.generateKey() byte[] key = assumeSupported(sop::generateKey)
.userId("Alice <alice@openpgp.org>") .userId("Alice <alice@openpgp.org>")
.noArmor() .noArmor()
.generate() .generate()
@ -54,7 +57,7 @@ public class GenerateKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void generateKeyWithMultipleUserIdsTest(SOP sop) throws IOException { public void generateKeyWithMultipleUserIdsTest(SOP sop) throws IOException {
byte[] key = sop.generateKey() byte[] key = assumeSupported(sop::generateKey)
.userId("Alice <alice@openpgp.org>") .userId("Alice <alice@openpgp.org>")
.userId("Bob <bob@openpgp.org>") .userId("Bob <bob@openpgp.org>")
.generate() .generate()
@ -67,7 +70,7 @@ public class GenerateKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void generateKeyWithoutUserIdTest(SOP sop) throws IOException { public void generateKeyWithoutUserIdTest(SOP sop) throws IOException {
byte[] key = sop.generateKey() byte[] key = assumeSupported(sop::generateKey)
.generate() .generate()
.getBytes(); .getBytes();
@ -78,7 +81,7 @@ public class GenerateKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void generateKeyWithPasswordTest(SOP sop) throws IOException { public void generateKeyWithPasswordTest(SOP sop) throws IOException {
byte[] key = sop.generateKey() byte[] key = assumeSupported(sop::generateKey)
.userId("Alice <alice@openpgp.org>") .userId("Alice <alice@openpgp.org>")
.withKeyPassword("sw0rdf1sh") .withKeyPassword("sw0rdf1sh")
.generate() .generate()
@ -91,7 +94,7 @@ public class GenerateKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void generateKeyWithMultipleUserIdsAndPassword(SOP sop) throws IOException { public void generateKeyWithMultipleUserIdsAndPassword(SOP sop) throws IOException {
byte[] key = sop.generateKey() byte[] key = assumeSupported(sop::generateKey)
.userId("Alice <alice@openpgp.org>") .userId("Alice <alice@openpgp.org>")
.userId("Bob <bob@openpgp.org>") .userId("Bob <bob@openpgp.org>")
.withKeyPassword("sw0rdf1sh") .withKeyPassword("sw0rdf1sh")
@ -105,17 +108,45 @@ public class GenerateKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void generateSigningOnlyKey(SOP sop) throws IOException { public void generateSigningOnlyKey(SOP sop) throws IOException {
byte[] signingOnlyKey = sop.generateKey() byte[] signingOnlyKey = assumeSupported(sop::generateKey)
.signingOnly() .signingOnly()
.userId("Alice <alice@pgpainless.org>") .userId("Alice <alice@pgpainless.org>")
.generate() .generate()
.getBytes(); .getBytes();
byte[] signingOnlyCert = sop.extractCert() byte[] signingOnlyCert = assumeSupported(sop::extractCert)
.key(signingOnlyKey) .key(signingOnlyKey)
.getBytes(); .getBytes();
assertThrows(SOPGPException.CertCannotEncrypt.class, () -> assertThrows(SOPGPException.CertCannotEncrypt.class, () ->
sop.encrypt().withCert(signingOnlyCert) assumeSupported(sop::encrypt).withCert(signingOnlyCert)
.plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8))); .plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8))
.toByteArrayAndResult()
.getBytes());
}
@ParameterizedTest
@MethodSource("provideInstances")
public void generateKeyWithSupportedProfiles(SOP sop) throws IOException {
List<Profile> profiles = assumeSupported(sop::listProfiles)
.generateKey();
for (Profile profile : profiles) {
generateKeyWithProfile(sop, profile.getName());
}
}
private void generateKeyWithProfile(SOP sop, String profile) throws IOException {
byte[] key;
try {
key = assumeSupported(sop::generateKey)
.profile(profile)
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
} catch (SOPGPException.UnsupportedProfile e) {
key = null;
}
assumeTrue(key != null, "'generate-key' does not support profile '" + profile + "'.");
JUtils.assertArrayStartsWith(key, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK);
} }
} }

View file

@ -36,12 +36,12 @@ public class InlineSignInlineDetachDetachedVerifyTest extends AbstractSOPTest {
public void inlineSignThenDetachThenDetachedVerifyTest(SOP sop) throws IOException { public void inlineSignThenDetachThenDetachedVerifyTest(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] inlineSigned = sop.inlineSign() byte[] inlineSigned = assumeSupported(sop::inlineSign)
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.getBytes(); .getBytes();
ByteArrayAndResult<Signatures> bytesAndResult = sop.inlineDetach() ByteArrayAndResult<Signatures> bytesAndResult = assumeSupported(sop::inlineDetach)
.message(inlineSigned) .message(inlineSigned)
.toByteArrayAndResult(); .toByteArrayAndResult();
@ -51,7 +51,7 @@ public class InlineSignInlineDetachDetachedVerifyTest extends AbstractSOPTest {
byte[] signatures = bytesAndResult.getResult() byte[] signatures = bytesAndResult.getResult()
.getBytes(); .getBytes();
List<Verification> verifications = sop.detachedVerify() List<Verification> verifications = assumeSupported(sop::detachedVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(signatures) .signatures(signatures)
.data(plaintext); .data(plaintext);
@ -64,12 +64,12 @@ public class InlineSignInlineDetachDetachedVerifyTest extends AbstractSOPTest {
public void inlineSignThenDetachNoArmorThenArmorThenDetachedVerifyTest(SOP sop) throws IOException { public void inlineSignThenDetachNoArmorThenArmorThenDetachedVerifyTest(SOP sop) throws IOException {
byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
byte[] inlineSigned = sop.inlineSign() byte[] inlineSigned = assumeSupported(sop::inlineSign)
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.getBytes(); .getBytes();
ByteArrayAndResult<Signatures> bytesAndResult = sop.inlineDetach() ByteArrayAndResult<Signatures> bytesAndResult = assumeSupported(sop::inlineDetach)
.noArmor() .noArmor()
.message(inlineSigned) .message(inlineSigned)
.toByteArrayAndResult(); .toByteArrayAndResult();
@ -81,12 +81,12 @@ public class InlineSignInlineDetachDetachedVerifyTest extends AbstractSOPTest {
.getBytes(); .getBytes();
Assertions.assertFalse(JUtils.arrayStartsWith(signatures, TestData.BEGIN_PGP_SIGNATURE)); Assertions.assertFalse(JUtils.arrayStartsWith(signatures, TestData.BEGIN_PGP_SIGNATURE));
byte[] armored = sop.armor() byte[] armored = assumeSupported(sop::armor)
.data(signatures) .data(signatures)
.getBytes(); .getBytes();
JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_SIGNATURE); JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_SIGNATURE);
List<Verification> verifications = sop.detachedVerify() List<Verification> verifications = assumeSupported(sop::detachedVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.signatures(armored) .signatures(armored)
.data(plaintext); .data(plaintext);

View file

@ -40,14 +40,14 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
public void inlineSignVerifyAlice(SOP sop) throws IOException { public void inlineSignVerifyAlice(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] inlineSigned = sop.inlineSign() byte[] inlineSigned = assumeSupported(sop::inlineSign)
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.getBytes(); .getBytes();
JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE); JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE);
ByteArrayAndResult<List<Verification>> bytesAndResult = sop.inlineVerify() ByteArrayAndResult<List<Verification>> bytesAndResult = assumeSupported(sop::inlineVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.data(inlineSigned) .data(inlineSigned)
.toByteArrayAndResult(); .toByteArrayAndResult();
@ -66,7 +66,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
public void inlineSignVerifyAliceNoArmor(SOP sop) throws IOException { public void inlineSignVerifyAliceNoArmor(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] inlineSigned = sop.inlineSign() byte[] inlineSigned = assumeSupported(sop::inlineSign)
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.noArmor() .noArmor()
.data(message) .data(message)
@ -74,7 +74,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
Assertions.assertFalse(JUtils.arrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE)); Assertions.assertFalse(JUtils.arrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE));
ByteArrayAndResult<List<Verification>> bytesAndResult = sop.inlineVerify() ByteArrayAndResult<List<Verification>> bytesAndResult = assumeSupported(sop::inlineVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.data(inlineSigned) .data(inlineSigned)
.toByteArrayAndResult(); .toByteArrayAndResult();
@ -93,7 +93,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
public void clearsignVerifyAlice(SOP sop) throws IOException { public void clearsignVerifyAlice(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] clearsigned = sop.inlineSign() byte[] clearsigned = assumeSupported(sop::inlineSign)
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.mode(InlineSignAs.clearsigned) .mode(InlineSignAs.clearsigned)
.data(message) .data(message)
@ -101,12 +101,13 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
JUtils.assertArrayStartsWith(clearsigned, TestData.BEGIN_PGP_SIGNED_MESSAGE); JUtils.assertArrayStartsWith(clearsigned, TestData.BEGIN_PGP_SIGNED_MESSAGE);
ByteArrayAndResult<List<Verification>> bytesAndResult = sop.inlineVerify() ByteArrayAndResult<List<Verification>> bytesAndResult = assumeSupported(sop::inlineVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.data(clearsigned) .data(clearsigned)
.toByteArrayAndResult(); .toByteArrayAndResult();
assertArrayEquals(message, bytesAndResult.getBytes()); assertArrayEquals(message, bytesAndResult.getBytes(),
"ASCII armored message does not appear to start with the 'BEGIN PGP SIGNED MESSAGE' header.");
List<Verification> verificationList = bytesAndResult.getResult(); List<Verification> verificationList = bytesAndResult.getResult();
VerificationListAssert.assertThatVerificationList(verificationList) VerificationListAssert.assertThatVerificationList(verificationList)
@ -121,7 +122,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8);
Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE;
ByteArrayAndResult<List<Verification>> bytesAndResult = sop.inlineVerify() ByteArrayAndResult<List<Verification>> bytesAndResult = assumeSupported(sop::inlineVerify)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.toByteArrayAndResult(); .toByteArrayAndResult();
@ -141,7 +142,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE;
Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec before sig Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec before sig
assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify() assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::inlineVerify)
.notBefore(afterSignature) .notBefore(afterSignature)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
@ -155,7 +156,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE;
Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before sig Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before sig
assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify() assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::inlineVerify)
.notAfter(beforeSignature) .notAfter(beforeSignature)
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
@ -167,14 +168,14 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
public void inlineSignVerifyBob(SOP sop) throws IOException { public void inlineSignVerifyBob(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] inlineSigned = sop.inlineSign() byte[] inlineSigned = assumeSupported(sop::inlineSign)
.key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.getBytes(); .getBytes();
JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE); JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE);
ByteArrayAndResult<List<Verification>> bytesAndResult = sop.inlineVerify() ByteArrayAndResult<List<Verification>> bytesAndResult = assumeSupported(sop::inlineVerify)
.cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8))
.data(inlineSigned) .data(inlineSigned)
.toByteArrayAndResult(); .toByteArrayAndResult();
@ -193,14 +194,14 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
public void inlineSignVerifyCarol(SOP sop) throws IOException { public void inlineSignVerifyCarol(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] inlineSigned = sop.inlineSign() byte[] inlineSigned = assumeSupported(sop::inlineSign)
.key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8))
.data(message) .data(message)
.getBytes(); .getBytes();
JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE); JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE);
ByteArrayAndResult<List<Verification>> bytesAndResult = sop.inlineVerify() ByteArrayAndResult<List<Verification>> bytesAndResult = assumeSupported(sop::inlineVerify)
.cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8))
.data(inlineSigned) .data(inlineSigned)
.toByteArrayAndResult(); .toByteArrayAndResult();
@ -219,14 +220,14 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
public void inlineSignVerifyProtectedKey(SOP sop) throws IOException { public void inlineSignVerifyProtectedKey(SOP sop) throws IOException {
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] inlineSigned = sop.inlineSign() byte[] inlineSigned = assumeSupported(sop::inlineSign)
.withKeyPassword(TestData.PASSWORD) .withKeyPassword(TestData.PASSWORD)
.key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8))
.mode(InlineSignAs.binary) .mode(InlineSignAs.binary)
.data(message) .data(message)
.getBytes(); .getBytes();
ByteArrayAndResult<List<Verification>> bytesAndResult = sop.inlineVerify() ByteArrayAndResult<List<Verification>> bytesAndResult = assumeSupported(sop::inlineVerify)
.cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8))
.data(inlineSigned) .data(inlineSigned)
.toByteArrayAndResult(); .toByteArrayAndResult();

View file

@ -26,8 +26,7 @@ public class ListProfilesTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void listGenerateKeyProfiles(SOP sop) { public void listGenerateKeyProfiles(SOP sop) {
List<Profile> profiles = sop List<Profile> profiles = assumeSupported(sop::listProfiles)
.listProfiles()
.generateKey(); .generateKey();
assertFalse(profiles.isEmpty()); assertFalse(profiles.isEmpty());
@ -36,8 +35,7 @@ public class ListProfilesTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void listEncryptProfiles(SOP sop) { public void listEncryptProfiles(SOP sop) {
List<Profile> profiles = sop List<Profile> profiles = assumeSupported(sop::listProfiles)
.listProfiles()
.encrypt(); .encrypt();
assertFalse(profiles.isEmpty()); assertFalse(profiles.isEmpty());
@ -46,8 +44,7 @@ public class ListProfilesTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void listUnsupportedProfiles(SOP sop) { public void listUnsupportedProfiles(SOP sop) {
assertThrows(SOPGPException.UnsupportedProfile.class, () -> sop assertThrows(SOPGPException.UnsupportedProfile.class, () -> assumeSupported(sop::listProfiles)
.listProfiles()
.subcommand("invalid")); .subcommand("invalid"));
} }
} }

View file

@ -0,0 +1,164 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.operation;
import kotlin.collections.ArraysKt;
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 = assumeSupported(sop::generateKey)
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] cert = assumeSupported(sop::extractCert)
.key(key)
.getBytes();
byte[] merged = assumeSupported(sop::mergeCerts)
.updates(cert)
.baseCertificates(cert)
.getBytes();
assertArrayEquals(cert, merged);
}
@ParameterizedTest
@MethodSource("provideInstances")
public void testMergeWithItselfArmored(SOP sop) throws IOException {
byte[] key = assumeSupported(sop::generateKey)
.noArmor()
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] cert = assumeSupported(sop::extractCert)
.key(key)
.getBytes();
byte[] merged = assumeSupported(sop::mergeCerts)
.updates(cert)
.baseCertificates(cert)
.getBytes();
assertArrayEquals(cert, merged);
}
@ParameterizedTest
@MethodSource("provideInstances")
public void testMergeWithItselfViaBase(SOP sop) throws IOException {
byte[] key = assumeSupported(sop::generateKey)
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] cert = assumeSupported(sop::extractCert)
.key(key)
.getBytes();
byte[] certs = ArraysKt.plus(cert, cert);
byte[] merged = assumeSupported(sop::mergeCerts)
.updates(cert)
.baseCertificates(certs)
.getBytes();
assertArrayEquals(cert, merged);
}
@ParameterizedTest
@MethodSource("provideInstances")
public void testApplyBaseToUpdate(SOP sop) throws IOException {
byte[] key = assumeSupported(sop::generateKey)
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] cert = assumeSupported(sop::extractCert)
.key(key)
.getBytes();
byte[] update = assumeSupported(sop::revokeKey)
.keys(key)
.getBytes();
byte[] merged = assumeSupported(sop::mergeCerts)
.updates(cert)
.baseCertificates(update)
.getBytes();
assertArrayEquals(update, merged);
}
@ParameterizedTest
@MethodSource("provideInstances")
public void testApplyUpdateToBase(SOP sop) throws IOException {
byte[] key = assumeSupported(sop::generateKey)
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] cert = assumeSupported(sop::extractCert)
.key(key)
.getBytes();
byte[] update = assumeSupported(sop::revokeKey)
.keys(key)
.getBytes();
byte[] merged = assumeSupported(sop::mergeCerts)
.updates(update)
.baseCertificates(cert)
.getBytes();
assertArrayEquals(update, merged);
}
@ParameterizedTest
@MethodSource("provideInstances")
public void testApplyUpdateToMissingBaseDoesNothing(SOP sop) throws IOException {
byte[] aliceKey = assumeSupported(sop::generateKey)
.userId("Alice <alice@pgpainless.org>")
.generate()
.getBytes();
byte[] aliceCert = assumeSupported(sop::extractCert)
.key(aliceKey)
.getBytes();
byte[] bobKey = assumeSupported(sop::generateKey)
.userId("Bob <bob@pgpainless.org>")
.generate()
.getBytes();
byte[] bobCert = assumeSupported(sop::extractCert)
.key(bobKey)
.getBytes();
byte[] merged = assumeSupported(sop::mergeCerts)
.updates(bobCert)
.baseCertificates(aliceCert)
.getBytes();
assertArrayEquals(aliceCert, merged);
}
}

View file

@ -36,8 +36,8 @@ public class RevokeKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void revokeUnprotectedKey(SOP sop) throws IOException { public void revokeUnprotectedKey(SOP sop) throws IOException {
byte[] secretKey = sop.generateKey().userId("Alice <alice@pgpainless.org>").generate().getBytes(); byte[] secretKey = assumeSupported(sop::generateKey).userId("Alice <alice@pgpainless.org>").generate().getBytes();
byte[] revocation = sop.revokeKey().keys(secretKey).getBytes(); byte[] revocation = assumeSupported(sop::revokeKey).keys(secretKey).getBytes();
assertTrue(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); assertTrue(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK));
assertFalse(Arrays.equals(secretKey, revocation)); assertFalse(Arrays.equals(secretKey, revocation));
@ -46,8 +46,8 @@ public class RevokeKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void revokeUnprotectedKeyNoArmor(SOP sop) throws IOException { public void revokeUnprotectedKeyNoArmor(SOP sop) throws IOException {
byte[] secretKey = sop.generateKey().userId("Alice <alice@pgpainless.org>").generate().getBytes(); byte[] secretKey = assumeSupported(sop::generateKey).userId("Alice <alice@pgpainless.org>").generate().getBytes();
byte[] revocation = sop.revokeKey().noArmor().keys(secretKey).getBytes(); byte[] revocation = assumeSupported(sop::revokeKey).noArmor().keys(secretKey).getBytes();
assertFalse(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); assertFalse(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK));
} }
@ -55,8 +55,8 @@ public class RevokeKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void revokeUnprotectedKeyUnarmored(SOP sop) throws IOException { public void revokeUnprotectedKeyUnarmored(SOP sop) throws IOException {
byte[] secretKey = sop.generateKey().userId("Alice <alice@pgpainless.org>").noArmor().generate().getBytes(); byte[] secretKey = assumeSupported(sop::generateKey).userId("Alice <alice@pgpainless.org>").noArmor().generate().getBytes();
byte[] revocation = sop.revokeKey().noArmor().keys(secretKey).getBytes(); byte[] revocation = assumeSupported(sop::revokeKey).noArmor().keys(secretKey).getBytes();
assertFalse(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); assertFalse(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK));
assertFalse(Arrays.equals(secretKey, revocation)); assertFalse(Arrays.equals(secretKey, revocation));
@ -65,18 +65,18 @@ public class RevokeKeyTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void revokeCertificateFails(SOP sop) throws IOException { public void revokeCertificateFails(SOP sop) throws IOException {
byte[] secretKey = sop.generateKey().generate().getBytes(); byte[] secretKey = assumeSupported(sop::generateKey).generate().getBytes();
byte[] certificate = sop.extractCert().key(secretKey).getBytes(); byte[] certificate = assumeSupported(sop::extractCert).key(secretKey).getBytes();
assertThrows(SOPGPException.BadData.class, () -> sop.revokeKey().keys(certificate).getBytes()); assertThrows(SOPGPException.BadData.class, () -> assumeSupported(sop::revokeKey).keys(certificate).getBytes());
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void revokeProtectedKey(SOP sop) throws IOException { public void revokeProtectedKey(SOP sop) throws IOException {
byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8);
byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice <alice@pgpainless.org>").generate().getBytes(); byte[] secretKey = assumeSupported(sop::generateKey).withKeyPassword(password).userId("Alice <alice@pgpainless.org>").generate().getBytes();
byte[] revocation = sop.revokeKey().withKeyPassword(password).keys(secretKey).getBytes(); byte[] revocation = assumeSupported(sop::revokeKey).withKeyPassword(password).keys(secretKey).getBytes();
assertFalse(Arrays.equals(secretKey, revocation)); assertFalse(Arrays.equals(secretKey, revocation));
} }
@ -86,8 +86,8 @@ public class RevokeKeyTest extends AbstractSOPTest {
public void revokeProtectedKeyWithMultiplePasswordOptions(SOP sop) throws IOException { public void revokeProtectedKeyWithMultiplePasswordOptions(SOP sop) throws IOException {
byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8);
byte[] wrongPassword = "0r4ng3".getBytes(UTF8Util.UTF8); byte[] wrongPassword = "0r4ng3".getBytes(UTF8Util.UTF8);
byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice <alice@pgpainless.org>").generate().getBytes(); byte[] secretKey = assumeSupported(sop::generateKey).withKeyPassword(password).userId("Alice <alice@pgpainless.org>").generate().getBytes();
byte[] revocation = sop.revokeKey().withKeyPassword(wrongPassword).withKeyPassword(password).keys(secretKey).getBytes(); byte[] revocation = assumeSupported(sop::revokeKey).withKeyPassword(wrongPassword).withKeyPassword(password).keys(secretKey).getBytes();
assertFalse(Arrays.equals(secretKey, revocation)); assertFalse(Arrays.equals(secretKey, revocation));
} }
@ -96,9 +96,9 @@ public class RevokeKeyTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void revokeProtectedKeyWithMissingPassphraseFails(SOP sop) throws IOException { public void revokeProtectedKeyWithMissingPassphraseFails(SOP sop) throws IOException {
byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8);
byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice <alice@pgpainless.org>").generate().getBytes(); byte[] secretKey = assumeSupported(sop::generateKey).withKeyPassword(password).userId("Alice <alice@pgpainless.org>").generate().getBytes();
assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.revokeKey().keys(secretKey).getBytes()); assertThrows(SOPGPException.KeyIsProtected.class, () -> assumeSupported(sop::revokeKey).keys(secretKey).getBytes());
} }
@ParameterizedTest @ParameterizedTest
@ -106,27 +106,27 @@ public class RevokeKeyTest extends AbstractSOPTest {
public void revokeProtectedKeyWithWrongPassphraseFails(SOP sop) throws IOException { public void revokeProtectedKeyWithWrongPassphraseFails(SOP sop) throws IOException {
byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8);
String wrongPassword = "or4ng3"; String wrongPassword = "or4ng3";
byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice <alice@pgpainless.org>").generate().getBytes(); byte[] secretKey = assumeSupported(sop::generateKey).withKeyPassword(password).userId("Alice <alice@pgpainless.org>").generate().getBytes();
assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.revokeKey().withKeyPassword(wrongPassword).keys(secretKey).getBytes()); assertThrows(SOPGPException.KeyIsProtected.class, () -> assumeSupported(sop::revokeKey).withKeyPassword(wrongPassword).keys(secretKey).getBytes());
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void revokeKeyIsNowHardRevoked(SOP sop) throws IOException { public void revokeKeyIsNowHardRevoked(SOP sop) throws IOException {
byte[] key = sop.generateKey().generate().getBytes(); byte[] key = assumeSupported(sop::generateKey).generate().getBytes();
byte[] cert = sop.extractCert().key(key).getBytes(); byte[] cert = assumeSupported(sop::extractCert).key(key).getBytes();
// Sign a message with the key // Sign a message with the key
byte[] msg = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] msg = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
byte[] signedMsg = sop.inlineSign().key(key).data(msg).getBytes(); byte[] signedMsg = assumeSupported(sop::inlineSign).key(key).data(msg).getBytes();
// Verifying the message with the valid cert works // Verifying the message with the valid cert works
List<Verification> result = sop.inlineVerify().cert(cert).data(signedMsg).toByteArrayAndResult().getResult(); List<Verification> result = assumeSupported(sop::inlineVerify).cert(cert).data(signedMsg).toByteArrayAndResult().getResult();
VerificationListAssert.assertThatVerificationList(result).hasSingleItem(); VerificationListAssert.assertThatVerificationList(result).hasSingleItem();
// Now hard revoke the key and re-check signature, expecting no valid certification // Now hard revoke the key and re-check signature, expecting no valid certification
byte[] revokedCert = sop.revokeKey().keys(key).getBytes(); byte[] revokedCert = assumeSupported(sop::revokeKey).keys(key).getBytes();
assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify().cert(revokedCert).data(signedMsg).toByteArrayAndResult()); assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::inlineVerify).cert(revokedCert).data(signedMsg).toByteArrayAndResult());
} }
} }

View file

@ -28,7 +28,7 @@ public class VersionTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void versionNameTest(SOP sop) { public void versionNameTest(SOP sop) {
String name = sop.version().getName(); String name = assumeSupported(sop::version).getName();
assertNotNull(name); assertNotNull(name);
assertFalse(name.isEmpty()); assertFalse(name.isEmpty());
} }
@ -36,21 +36,21 @@ public class VersionTest extends AbstractSOPTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void versionVersionTest(SOP sop) { public void versionVersionTest(SOP sop) {
String version = sop.version().getVersion(); String version = assumeSupported(sop::version).getVersion();
assertFalse(version.isEmpty()); assertFalse(version.isEmpty());
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void backendVersionTest(SOP sop) { public void backendVersionTest(SOP sop) {
String backend = sop.version().getBackendVersion(); String backend = assumeSupported(sop::version).getBackendVersion();
assertFalse(backend.isEmpty()); assertFalse(backend.isEmpty());
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void extendedVersionTest(SOP sop) { public void extendedVersionTest(SOP sop) {
String extended = sop.version().getExtendedVersion(); String extended = assumeSupported(sop::version).getExtendedVersion();
assertFalse(extended.isEmpty()); assertFalse(extended.isEmpty());
} }
@ -58,27 +58,27 @@ public class VersionTest extends AbstractSOPTest {
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void sopSpecVersionTest(SOP sop) { public void sopSpecVersionTest(SOP sop) {
try { try {
sop.version().getSopSpecVersion(); assumeSupported(sop::version).getSopSpecVersion();
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new TestAbortedException("SOP backend does not support 'version --sop-spec' yet."); throw new TestAbortedException("SOP backend does not support 'version --sop-spec' yet.");
} }
String sopSpec = sop.version().getSopSpecVersion(); String sopSpec = assumeSupported(sop::version).getSopSpecVersion();
if (sop.version().isSopSpecImplementationIncomplete()) { if (assumeSupported(sop::version).isSopSpecImplementationIncomplete()) {
assertTrue(sopSpec.startsWith("~draft-dkg-openpgp-stateless-cli-")); assertTrue(sopSpec.startsWith("~draft-dkg-openpgp-stateless-cli-"));
} else { } else {
assertTrue(sopSpec.startsWith("draft-dkg-openpgp-stateless-cli-")); assertTrue(sopSpec.startsWith("draft-dkg-openpgp-stateless-cli-"));
} }
int sopRevision = sop.version().getSopSpecRevisionNumber(); int sopRevision = assumeSupported(sop::version).getSopSpecRevisionNumber();
assertTrue(sop.version().getSopSpecRevisionName().endsWith("" + sopRevision)); assertTrue(assumeSupported(sop::version).getSopSpecRevisionName().endsWith("" + sopRevision));
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("provideInstances") @MethodSource("provideInstances")
public void sopVVersionTest(SOP sop) { public void sopVVersionTest(SOP sop) {
try { try {
sop.version().getSopVVersion(); assumeSupported(sop::version).getSopVVersion();
} catch (SOPGPException.UnsupportedOption e) { } catch (SOPGPException.UnsupportedOption e) {
throw new TestAbortedException( throw new TestAbortedException(
"Implementation does (gracefully) not provide coverage for any sopv interface version."); "Implementation does (gracefully) not provide coverage for any sopv interface version.");
@ -86,4 +86,10 @@ public class VersionTest extends AbstractSOPTest {
throw new TestAbortedException("Implementation does not provide coverage for any sopv interface version."); throw new TestAbortedException("Implementation does not provide coverage for any sopv interface version.");
} }
} }
@ParameterizedTest
@MethodSource("provideInstances")
public void sopJavaVersionTest(SOP sop) {
assertNotNull(assumeSupported(sop::version).getSopJavaVersion());
}
} }

View file

@ -20,17 +20,22 @@ import sop.util.UTF8Util
* in the IETF namespace that begins with the string `draft-` should have semantics that hew as * in the IETF namespace that begins with the string `draft-` should have semantics that hew as
* closely as possible to the referenced Internet Draft. * closely as possible to the referenced Internet Draft.
* @param description a free-form description of the profile. * @param description a free-form description of the profile.
* @see <a * @param aliases list of optional profile alias names
* href="https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html#name-profile"> * @see
* SOP Spec - Profile</a> * [SOP Spec - Profile](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html#name-profile)
*/ */
data class Profile(val name: String, val description: Optional<String>) { data class Profile(
val name: String,
val description: Optional<String>,
val aliases: List<String> = listOf()
) {
@JvmOverloads @JvmOverloads
constructor( constructor(
name: String, name: String,
description: String? = null description: String? = null,
) : this(name, Optional.ofNullable(description?.trim()?.ifBlank { null })) aliases: List<String> = listOf()
) : this(name, Optional.ofNullable(description?.trim()?.ifBlank { null }), aliases)
init { init {
require(name.trim().isNotBlank()) { "Name cannot be empty." } require(name.trim().isNotBlank()) { "Name cannot be empty." }
@ -45,13 +50,33 @@ data class Profile(val name: String, val description: Optional<String>) {
fun hasDescription() = description.isPresent fun hasDescription() = description.isPresent
/**
* Return a copy of this [Profile] with the aliases set to the given strings.
*
* @param alias one or more alias names
* @return profile with aliases
*/
fun withAliases(vararg alias: String): Profile {
return Profile(name, description, alias.toList())
}
/** /**
* Convert the profile into a String for displaying. * Convert the profile into a String for displaying.
* *
* @return string * @return string
*/ */
override fun toString(): String = override fun toString(): String = buildString {
if (description.isEmpty) name else "$name: ${description.get()}" append(name)
if (!description.isEmpty || !aliases.isEmpty()) {
append(":")
}
if (!description.isEmpty) {
append(" ${description.get()}")
}
if (!aliases.isEmpty()) {
append(" (aliases: ${aliases.joinToString(separator = ", ")})")
}
}
companion object { companion object {
@ -64,9 +89,21 @@ data class Profile(val name: String, val description: Optional<String>) {
@JvmStatic @JvmStatic
fun parse(string: String): Profile { fun parse(string: String): Profile {
return if (string.contains(": ")) { return if (string.contains(": ")) {
Profile( val name = string.substring(0, string.indexOf(": "))
string.substring(0, string.indexOf(": ")), var description = string.substring(string.indexOf(": ") + 2).trim()
string.substring(string.indexOf(": ") + 2).trim()) if (description.contains("(aliases: ")) {
val aliases =
description.substring(
description.indexOf("(aliases: ") + 10, description.indexOf(")"))
description = description.substring(0, description.indexOf("(aliases: ")).trim()
Profile(name, description, aliases.split(", ").toList())
} else {
if (description.isNotBlank()) {
Profile(name, description)
} else {
Profile(name)
}
}
} else if (string.endsWith(":")) { } else if (string.endsWith(":")) {
Profile(string.substring(0, string.length - 1)) Profile(string.substring(0, string.length - 1))
} else { } else {

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,57 @@ 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?
} }

View file

@ -6,29 +6,47 @@ package sop
import sop.operation.DetachedVerify import sop.operation.DetachedVerify
import sop.operation.InlineVerify import sop.operation.InlineVerify
import sop.operation.ValidateUserId
import sop.operation.Version import sop.operation.Version
/** Subset of [SOP] implementing only OpenPGP signature verification. */ /** Subset of [SOP] implementing only OpenPGP signature verification. */
interface SOPV { interface SOPV {
/** Get information about the implementations name and version. */ /**
fun version(): Version * Get information about the implementations name and version.
*
* @since sopv 1.0
*/
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.
*
* @since sopv 1.0
*/ */
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.
*
* @since sopv 1.0
*/ */
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.
*
* @since sopv 1.0
*/ */
fun inlineVerify(): InlineVerify fun inlineVerify(): InlineVerify?
/**
* Validate a UserID in an OpenPGP certificate.
*
* @since sopv 1.2
*/
fun validateUserId(): ValidateUserId?
} }

View file

@ -10,13 +10,23 @@ import sop.enums.SignatureMode
import sop.util.Optional import sop.util.Optional
import sop.util.UTCUtil import sop.util.UTCUtil
/**
* Metadata about a verified signature.
*
* @param creationTime creation time of the signature
* @param signingKeyFingerprint fingerprint of the (sub-)key that issued the signature
* @param signingCertFingerprint fingerprint of the certificate that contains the signing key
* @param signatureMode optional signature mode (text/binary)
* @param jsonOrDescription arbitrary text or JSON data
*/
data class Verification( data class Verification(
val creationTime: Date, val creationTime: Date,
val signingKeyFingerprint: String, val signingKeyFingerprint: String,
val signingCertFingerprint: String, val signingCertFingerprint: String,
val signatureMode: Optional<SignatureMode>, val signatureMode: Optional<SignatureMode>,
val description: Optional<String> val jsonOrDescription: Optional<String>
) { ) {
@JvmOverloads @JvmOverloads
constructor( constructor(
creationTime: Date, creationTime: Date,
@ -31,10 +41,49 @@ data class Verification(
Optional.ofNullable(signatureMode), Optional.ofNullable(signatureMode),
Optional.ofNullable(description?.trim())) Optional.ofNullable(description?.trim()))
@JvmOverloads
constructor(
creationTime: Date,
signingKeyFingerprint: String,
signingCertFingerprint: String,
signatureMode: SignatureMode? = null,
json: JSON,
jsonSerializer: JSONSerializer
) : this(
creationTime,
signingKeyFingerprint,
signingCertFingerprint,
Optional.ofNullable(signatureMode),
Optional.of(jsonSerializer.serialize(json)))
@Deprecated("Replaced by jsonOrDescription", replaceWith = ReplaceWith("jsonOrDescription"))
val description = jsonOrDescription
/** This value is `true` if the [Verification] contains extension JSON. */
val containsJson: Boolean =
jsonOrDescription.get()?.trim()?.let { it.startsWith("{") && it.endsWith("}") } ?: false
/**
* Attempt to parse the [jsonOrDescription] field using the provided [JSONParser] and return the
* result. This method returns `null` if parsing fails.
*
* @param parser [JSONParser] implementation
* @return successfully parsed [JSON] POJO or `null`.
*/
fun getJson(parser: JSONParser): JSON? {
return jsonOrDescription.get()?.let {
try {
parser.parse(it)
} catch (e: ParseException) {
null
}
}
}
override fun toString(): String = override fun toString(): String =
"${UTCUtil.formatUTCDate(creationTime)} $signingKeyFingerprint $signingCertFingerprint" + "${UTCUtil.formatUTCDate(creationTime)} $signingKeyFingerprint $signingCertFingerprint" +
(if (signatureMode.isPresent) " mode:${signatureMode.get()}" else "") + (if (signatureMode.isPresent) " mode:${signatureMode.get()}" else "") +
(if (description.isPresent) " ${description.get()}" else "") (if (jsonOrDescription.isPresent) " ${jsonOrDescription.get()}" else "")
companion object { companion object {
@JvmStatic @JvmStatic
@ -73,4 +122,50 @@ data class Verification(
} }
} }
} }
/**
* POJO data class representing JSON metadata.
*
* @param signers list of supplied CERTS objects that could have issued the signature,
* identified by the name given on the command line.
* @param comment a freeform UTF-8 encoded text describing the verification
* @param ext an extension object containing arbitrary, implementation-specific data
*/
data class JSON(val signers: List<String>, val comment: String?, val ext: Any?) {
/** Create a JSON object with only a list of signers. */
constructor(signers: List<String>) : this(signers, null, null)
/** Create a JSON object with only a single signer. */
constructor(signer: String) : this(listOf(signer))
}
/** Interface abstracting a JSON parser that parses [JSON] POJOs from single-line strings. */
fun interface JSONParser {
/**
* Parse a [JSON] POJO from the given single-line [string]. If the string does not represent
* a JSON object matching the [JSON] definition, this method throws a [ParseException].
*
* @param string [String] representation of the [JSON] object.
* @return parsed [JSON] POJO
* @throws ParseException if the [string] is not a JSON string representing the [JSON]
* object.
*/
@Throws(ParseException::class) fun parse(string: String): JSON
}
/**
* Interface abstracting a JSON serializer that converts [JSON] POJOs into single-line JSON
* strings.
*/
fun interface JSONSerializer {
/**
* Serialize the given [JSON] object into a single-line JSON string.
*
* @param json JSON POJO
* @return JSON string
*/
fun serialize(json: JSON): String
}
} }

Some files were not shown because too many files have changed in this diff Show more