Compare commits

..

70 commits

Author SHA1 Message Date
7ee856f9b9
Remove call to explicitly set bundle to fix native image 2025-04-08 13:13:15 +02:00
b42b966866
Fix documentation of merge-certs command 2025-04-08 12:44:35 +02:00
0b68d9f04e
Bump version 2025-04-08 12:44:34 +02:00
db315e9ed2
Document endOfOptionsDelimiter 2025-04-08 12:44:19 +02:00
966ea3c3a6
Fix nullability of sop commands 2025-04-08 12:44:19 +02:00
f6d7af2dab
Add first test for new commands 2025-04-08 12:44:19 +02:00
3367450f94
Show endOfOptions delimiter in help 2025-04-08 12:44:19 +02:00
54782d2a22
Implement external variants of new subcommands 2025-04-08 12:44:19 +02:00
83eafd8817
Checkstyle 2025-04-08 12:44:19 +02:00
6c7fcfccce
Implement validate-userid command 2025-04-08 12:44:18 +02:00
04f7a5d30d
Update msg files with input/output information 2025-04-08 12:44:18 +02:00
88d3301daa
Checkstyle and exception handling improvements 2025-04-08 12:44:18 +02:00
6fc849bb6a
Implement certify-userid command 2025-04-08 12:44:18 +02:00
5bebdb779a
Add support for rendering help info for input and output 2025-04-08 12:44:17 +02:00
3e6e47ed50
Add implementation of merge-certs command 2025-04-08 12:41:08 +02:00
023b4ea45b
Add implementation of update-key command 2025-04-08 12:41:08 +02:00
dd07d10350
Add new exceptions 2025-04-08 12:41:07 +02:00
859bb5bdde
Fix issues in kdoc 2025-04-04 12:16:00 +02:00
edb405d79e
Add TODO to remove ProxyOutputStream in 11.X 2025-04-04 12:11:20 +02:00
57e2f8391b
Update CHANGELOG 2025-04-04 10:43:49 +02:00
51ba24ddbe
Enable kapt annotation processing to properly embed picocli configuration files for native images into the -cli jar file
For this it is apparently necessary to upgrade kotlin to 1.9.21
See https://stackoverflow.com/a/79030947/11150851
2025-04-03 14:18:46 +02:00
d1893c5ea0
SOP-Java 10.1.1-SNAPSHOT 2025-03-11 21:22:31 +01:00
c145f8bb37
SOP-Java 10.1.0 2025-03-11 21:19:41 +01:00
924cfaa140
Update README 2025-03-11 21:18:24 +01:00
f2602bb413
Bump version to 10.1.0-SNAPSHOT 2025-03-11 21:04:50 +01:00
97e91f50ab
Migrate pipeline definition to use from_secret
https://woodpecker-ci.org/docs/usage/secrets\#use-secrets-in-settings-and-environment
2025-01-28 12:34:53 +01:00
690ba6dc16
Add rpgpie-sop reference in README 2025-01-28 12:34:53 +01:00
9ec3cc911b
Add bcsop reference in README 2025-01-28 12:34:53 +01:00
f92a73a5ad
Add back legacy --verify-out option alias for decrypt cmd 2025-01-28 12:34:53 +01:00
2b6015f59a
Add license header to properties files 2025-01-28 12:34:52 +01:00
84e381fe8e
Write sop-java version to sop-java-version.properties 2025-01-28 12:34:52 +01:00
b1e1a2283f
Update changelog 2025-01-28 12:34:51 +01:00
b3b8da4e35
Move testfixtures to own artifact 2025-01-28 12:33:07 +01:00
ca65cbe668
For now, do not re-set msg bundle (graal) 2025-01-28 12:33:06 +01:00
4eb6d1fdcb
Prevent unmatched parameters when setting locale 2025-01-28 12:33:06 +01:00
594b9029b2
Document logback spam 2025-01-28 12:33:06 +01:00
471947ef9c
Fix woodpecker warnings 2025-01-28 12:33:06 +01:00
1fd3161851
Properly match MissingArg exception code 2025-01-28 12:33:06 +01:00
a8a753536a
Add translations for new hardware exception error messages 2025-01-28 12:33:06 +01:00
eadea08d3c
Add new SOPGPException types related to hardware modules 2025-01-28 12:33:05 +01:00
547acdb740
Remove label() option from armor() operation 2025-01-28 12:33:05 +01:00
bb026bcbeb
Mark ProxyOutputStream as deprecated 2025-01-28 12:33:05 +01:00
e7778cb0d2
Remove deprecated junit5-system-exit
Replaced with custom test DSL that avoids System.exit
2025-01-28 12:33:05 +01:00
ac00b68694
Add description of external-sop module 2025-01-28 12:33:04 +01:00
e6c9d6f43d
SOP-Java 10.0.4-SNAPSHOT 2024-10-31 14:06:37 +01:00
c136d40fa7
SOP-Java 10.0.3 2024-10-31 13:54:31 +01:00
f35fd6c1ae
Update changelog 2024-10-31 13:53:57 +01:00
375dd65789
revoke-key command: Allow for multiple '--with-key-password' options 2024-10-31 13:48:55 +01:00
42a16a4f6d
Fix password parameter passing in change-key-password 2024-10-31 13:48:32 +01:00
b3f446fe8d
SOP-Java 10.0.3-SNAPSHOT 2024-10-14 16:22:06 +02:00
1958614fac
SOP-Java 10.0.2 2024-10-14 16:20:33 +02:00
a09f10fe85
Update changelog 2024-10-14 15:50:40 +02:00
a90f9be0e4
Downgrade logback-core to 1.2.13 2024-10-14 15:50:31 +02:00
63d8045224
Update changelog 2024-10-14 14:42:00 +02:00
7014dbcfb7
SOP-Java 10.0.2-SNAPSHOT 2024-10-14 14:36:41 +02:00
354ef8841a
SOP-Java 10.0.1 2024-10-14 13:46:48 +02:00
261ac212b8
Update changelog 2024-10-14 13:44:17 +02:00
f7530e3263
Bump logback to 1.4.14 2024-10-14 13:43:45 +02:00
8d7e89098f
Update changelog 2024-10-14 13:40:25 +02:00
a523270395
Update spec revision and badge link 2024-10-14 13:39:06 +02:00
d25a424adc
Update changelog 2024-10-14 13:34:56 +02:00
2d4bc24c64
Abort tests on UnsupportedOption 2024-10-14 13:32:28 +02:00
65945e0094
Fix external-sop decrypt --verifications-out 2024-10-14 13:32:11 +02:00
4388f00dc0
Fix NPE in DecryptExternal when reading lines 2024-10-14 12:36:26 +02:00
1df5747549
EncryptExternal: Fix parameter passing for --sign-with option 2024-10-14 12:33:58 +02:00
ae2389cabf
Bump version to 10.0.0 2024-03-17 18:06:50 +01:00
34a05e96a1
Move signature verification operations to sopv interface subset 2024-03-17 18:06:50 +01:00
7b04275625
Add test ckecking that BadData is thrown if KEYS is passed for CERTS 2024-03-17 18:06:50 +01:00
a0e7356757
Replace assumeTrue(false) with explicit TestAbortedException 2024-03-17 18:06:50 +01:00
173bc55eb9
Fix javadoc reference 2024-03-17 18:06:49 +01:00
44 changed files with 177 additions and 38 deletions

View file

@ -16,4 +16,6 @@ steps:
- gradle check javadocAll
# Code has coverage
- gradle jacocoRootReport coveralls
secrets: [coveralls_repo_token]
environment:
coveralls_repo_token:
from_secret: coveralls_repo_token

View file

@ -6,10 +6,37 @@ SPDX-License-Identifier: Apache-2.0
# Changelog
## 10.0.1-SNAPSHOT
## 10.1.1-SNAPSHOT
- Prepare jar files for use in native images, e.g. using GraalVM by generating and including
configuration files for reflection, resources and dynamic proxies.
## 10.1.0
- `sop-java`:
- Remove `label()` option from `armor()` subcommand
- Move test-fixtures artifact built with the `testFixtures` plugin into
its own module `sop-java-testfixtures`, which can be consumed by maven builds.
- `sop-java-picocli`:
- Properly map `MissingParameterException` to `MissingArg` exit code
- As a workaround for native builds using graalvm:
- Do not re-set message bundles dynamically (fails in native builds)
- Prevent an unmatched argument error
## 10.0.3
- CLI `change-key-password`: Fix indirect parameter passing for new and old passwords (thanks to @dkg for the report)
- Backport: `revoke-key`: Allow for multiple password options
## 10.0.2
- Downgrade `logback-core` to `1.2.13`
## 10.0.1
- Remove `label()` option from `Armor` operation
- Fix exit code for 'Missing required option/parameter' error
- Fix `revoke-key`: Allow for multiple invocations of `--with-key-password` option
- Fix `EncryptExternal` use of `--sign-with` parameter
- Fix `NullPointerException` in `DecryptExternal` when reading lines
- Fix `DecryptExternal` use of `verifications-out`
- Test suite: Ignore tests if `UnsupportedOption` is thrown
- Bump `logback-core` to `1.4.14`
## 10.0.0
- Update implementation to [SOP Specification revision 10](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-10.html).
@ -17,6 +44,10 @@ SPDX-License-Identifier: Apache-2.0
- Introduce `sopv` interface subset with revision `1.0`
- Add `sop version --sopv`
## 8.0.2
- CLI `change-key-password`: Fix indirect parameter passing for new and old passwords (thanks to @dkg for the report)
- Backport: `revoke-key`: Allow for multiple password options
## 8.0.1
- `decrypt`: Do not throw `NoSignature` exception (exit code 3) if `--verify-with` is provided, but `VERIFICATIONS` is empty.
@ -35,6 +66,13 @@ SPDX-License-Identifier: Apache-2.0
- Change `EncryptAs` values into lowercase
- Change `SignAs` values into lowercase
## 7.0.2
- CLI `change-key-password`: Fix indirect parameter passing for new and old passwords (thanks to @dkg for the report)
- Backport: revoke-key command: Allow for multiple '--with-key-password' options
## 7.0.1
- `decrypt`: Do not throw `NoSignature` exception (exit code 3) if `--verify-with` is provided, but `VERIFICATIONS` is empty.
## 7.0.0
- Update implementation to [SOP Specification revision 07](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-07.html).
- Add support for new `revoke-key` subcommand

View file

@ -27,6 +27,8 @@ The repository contains the following modules:
compatible with the SOP-CLI specification.
* [external-sop](/external-sop) contains an API implementation that can be used to forward API calls to a SOP executable,
allowing to delegate the implementation logic to an arbitrary SOP CLI implementation.
* [sop-java-testfixtures](/sop-java-testfixtures) contains a test suite that can be shared by downstream implementations
of `sop-java`.
## Known Implementations
(Please expand!)
@ -35,9 +37,11 @@ allowing to delegate the implementation logic to an arbitrary SOP CLI implementa
|-------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|
| [pgpainless-sop](https://github.com/pgpainless/pgpainless/tree/main/pgpainless-sop) | Implementation of `sop-java` using PGPainless |
| [external-sop](https://github.com/pgpainless/sop-java/tree/main/external-sop) | Implementation of `sop-java` that allows binding to external SOP binaries such as `sqop` |
| [bcsop](https://codeberg.org/PGPainless/bc-sop) | Implementation of `sop-java` using vanilla Bouncy Castle |
### Implementations in other languages
| Project | Language |
|-------------------------------------------------|----------|
| [sop-rs](https://sequoia-pgp.gitlab.io/sop-rs/) | Rust |
| [SOP for python](https://pypi.org/project/sop/) | Python |
| Project | Language |
|---------------------------------------------------|----------|
| [sop-rs](https://sequoia-pgp.gitlab.io/sop-rs/) | Rust |
| [SOP for python](https://pypi.org/project/sop/) | Python |
| [rpgpie-sop](https://crates.io/crates/rpgpie-sop) | Rust |

View file

@ -19,7 +19,7 @@ buildscript {
plugins {
id 'ru.vyarus.animalsniffer' version '1.5.3'
id 'org.jetbrains.kotlin.jvm' version "1.8.10"
id 'org.jetbrains.kotlin.jvm' version "1.9.21"
id 'com.diffplug.spotless' version '6.22.0' apply false
}
@ -32,6 +32,7 @@ allprojects {
apply plugin: 'jacoco'
apply plugin: 'checkstyle'
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.diffplug.spotless'
// For non-cli modules enable android api compatibility check

View file

@ -27,7 +27,7 @@ dependencies {
// The ExternalTestSubjectFactory reads json config file to find configured SOP binaries...
testImplementation "com.google.code.gson:gson:$gsonVersion"
// ...and extends TestSubjectFactory
testImplementation(testFixtures(project(":sop-java")))
testImplementation(project(":sop-java-testfixtures"))
}
test {

View file

@ -78,7 +78,7 @@ class DecryptExternal(
val verifyOut = File(tempDir, "verifications-out")
verifyOut.delete()
if (requireVerification) {
commandList.add("--verify-out=${verifyOut.absolutePath}")
commandList.add("--verifications-out=${verifyOut.absolutePath}")
}
try {

View file

@ -37,7 +37,7 @@ class EncryptExternal(
override fun signWith(key: InputStream): Encrypt = apply {
commandList.add("--sign-with=@ENV:SIGN_WITH_$argCounter")
envList.add("SIGN_WITH_$argCounter=${ExternalSOP.readString(key)}")
envList.add("SIGN_WITH_$argCounter=${readString(key)}")
argCounter += 1
}

View file

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

View file

@ -17,11 +17,11 @@ dependencies {
// SOP
implementation(project(":sop-java"))
testImplementation(testFixtures(project(":sop-java")))
testImplementation(project(":sop-java-testfixtures"))
// CLI
implementation "info.picocli:picocli:$picocliVersion"
annotationProcessor "info.picocli:picocli-codegen:$picocliVersion"
kapt "info.picocli:picocli-codegen:$picocliVersion"
// @Nonnull, @Nullable...
implementation "com.google.code.findbugs:jsr305:$jsrVersion"
@ -33,6 +33,10 @@ application {
mainClass = mainClassName
}
compileJava {
options.compilerArgs += ["-Aproject=${project.group}/${project.name}"]
}
jar {
dependsOn(":sop-java:jar")
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)

View file

@ -64,7 +64,7 @@ class SopCLI {
@JvmField var EXECUTABLE_NAME = "sop"
@JvmField
@Option(names = ["--stacktrace"], scope = CommandLine.ScopeType.INHERIT)
@Option(names = ["--stacktrace"], scope = ScopeType.INHERIT)
var stacktrace = false
@JvmStatic
@ -78,33 +78,25 @@ class SopCLI {
@JvmStatic
fun execute(vararg args: String): Int {
// Set locale
CommandLine(InitLocale()).parseArgs(*args)
CommandLine(InitLocale()).setUnmatchedArgumentsAllowed(true).parseArgs(*args)
// Re-set bundle with updated locale
cliMsg = ResourceBundle.getBundle("msg_sop")
val cmd =
CommandLine(SopCLI::class.java).apply {
// explicitly set help command resource bundle
subcommands["help"]?.setResourceBundle(ResourceBundle.getBundle("msg_help"))
return CommandLine(SopCLI::class.java)
.apply {
// Hide generate-completion command
subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true)
// render Input/Output sections in help command
subcommands.values.filter { (it.getCommand() as Any) is AbstractSopCmd } // Only for AbstractSopCmd objects
.forEach { (it.getCommand() as AbstractSopCmd).installIORenderer(it) }
// overwrite executable name
commandName = EXECUTABLE_NAME
// setup exception handling
executionExceptionHandler = SOPExecutionExceptionHandler()
exitCodeExceptionMapper = SOPExceptionExitCodeMapper()
isCaseInsensitiveEnumValuesAllowed = true
}
// render Input/Output sections in help command
cmd.subcommands.values
.filter {
(it.getCommand() as Any) is AbstractSopCmd
} // Only for AbstractSopCmd objects
.forEach { (it.getCommand() as AbstractSopCmd).installIORenderer(it) }
return cmd.execute(*args)
}.execute(*args)
}
}

View file

@ -33,9 +33,15 @@ class ChangeKeyPasswordCmd : AbstractSopCmd() {
changeKeyPassword.noArmor()
}
oldKeyPasswords.forEach { changeKeyPassword.oldKeyPassphrase(it) }
oldKeyPasswords.forEach {
val password = stringFromInputStream(getInput(it))
changeKeyPassword.oldKeyPassphrase(password)
}
newKeyPassword?.let { changeKeyPassword.newKeyPassphrase(it) }
newKeyPassword?.let {
val password = stringFromInputStream(getInput(it))
changeKeyPassword.newKeyPassphrase(password)
}
try {
changeKeyPassword.keys(System.`in`).writeTo(System.out)

View file

@ -29,7 +29,7 @@ class DecryptCmd : AbstractSopCmd() {
@Option(names = [OPT_WITH_PASSWORD], paramLabel = "PASSWORD")
var withPassword: List<String> = listOf()
@Option(names = [OPT_VERIFICATIONS_OUT], paramLabel = "VERIFICATIONS")
@Option(names = [OPT_VERIFICATIONS_OUT, "--verify-out"], paramLabel = "VERIFICATIONS")
var verifyOut: String? = null
@Option(names = [OPT_VERIFY_WITH], paramLabel = "CERT") var certs: List<String> = listOf()

View file

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
plugins {
id 'java-library'
}
group 'org.pgpainless'
repositories {
mavenCentral()
}
dependencies {
implementation(project(":sop-java"))
implementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
implementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
runtimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// @Nullable, @Nonnull annotations
implementation "com.google.code.findbugs:jsr305:3.0.2"
}

View file

@ -5,8 +5,11 @@
package sop.testsuite.operation;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.provider.Arguments;
import sop.SOP;
import sop.testsuite.AbortOnUnsupportedOption;
import sop.testsuite.AbortOnUnsupportedOptionExtension;
import sop.testsuite.SOPInstanceFactory;
import java.lang.reflect.InvocationTargetException;
@ -15,6 +18,8 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
@ExtendWith(AbortOnUnsupportedOptionExtension.class)
@AbortOnUnsupportedOption
public abstract class AbstractSOPTest {
private static final List<Arguments> backends = new ArrayList<>();

View file

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

View file

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

View file

@ -1,10 +1,12 @@
import org.apache.tools.ant.filters.ReplaceTokens
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
import org.apache.tools.ant.filters.*
plugins {
id 'java-library'
id 'java-test-fixtures'
}
group 'org.pgpainless'
@ -17,14 +19,19 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
testFixturesImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testFixturesImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testImplementation(project(":sop-java-testfixtures"))
// @Nullable, @Nonnull annotations
implementation "com.google.code.findbugs:jsr305:3.0.2"
}
processResources {
filter ReplaceTokens, tokens: [
"project.version": project.version.toString()
]
}
test {
useJUnitPlatform()
}

View file

@ -9,8 +9,9 @@ package sop
*
* @param micAlg string identifying the digest mechanism used to create the signed message. This is
* useful for setting the `micalg=` parameter for the multipart/signed content-type of a PGP/MIME
* object as described in section 5 of [RFC3156]. If more than one signature was generated and
* different digest mechanisms were used, the value of the micalg object is an empty string.
* object as described in section 5 of [RFC3156](https://www.rfc-editor.org/rfc/rfc3156#section-5).
* If more than one signature was generated and different digest mechanisms were used, the value
* of the micalg object is an empty string.
*/
data class SigningResult(val micAlg: MicAlg) {

View file

@ -78,7 +78,7 @@ interface GenerateKey {
fun signingOnly(): GenerateKey
/**
* Generate the OpenPGP key and return it encoded as an [InputStream].
* Generate the OpenPGP key and return it encoded as an [java.io.InputStream].
*
* @return key
* @throws MissingArg if no user-id was provided

View file

@ -4,6 +4,9 @@
package sop.operation
import java.io.IOException
import java.io.InputStream
import java.util.*
import kotlin.jvm.Throws
import sop.exception.SOPGPException
@ -107,4 +110,17 @@ interface Version {
* this method throws an [SOPGPException.UnsupportedOption] instead.
*/
@Throws(SOPGPException.UnsupportedOption::class) fun getSopVVersion(): String
/** Return the current version of the SOP-Java library. */
fun getSopJavaVersion(): String? {
return try {
val resourceIn: InputStream =
javaClass.getResourceAsStream("/sop-java-version.properties")
?: throw IOException("File sop-java-version.properties not found.")
val properties = Properties().apply { load(resourceIn) }
properties.getProperty("sop-java-version")
} catch (e: IOException) {
null
}
}
}

View file

@ -16,6 +16,7 @@ import java.io.OutputStream
* target output stream is not yet known.
*/
@Deprecated("Marked for removal.")
// TODO: Remove in 11.X
class ProxyOutputStream : OutputStream() {
private val buffer = ByteArrayOutputStream()
private var swapped: OutputStream? = null

View file

@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2025 Paul Schaub <info@pgpainless.org>
#
# SPDX-License-Identifier: Apache-2.0
sop-java-version=@project.version@

View file

@ -12,7 +12,7 @@ allprojects {
jsrVersion = '3.0.2'
junitVersion = '5.8.2'
junitSysExitVersion = '1.1.2'
logbackVersion = '1.2.13'
logbackVersion = '1.2.13' // 1.4+ cause CLI spam
mockitoVersion = '4.5.1'
picocliVersion = '4.6.3'
slf4jVersion = '1.7.36'