1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-10 02:39:39 +02:00

Compare commits

..

No commits in common. "main" and "1.7.2" have entirely different histories.
main ... 1.7.2

55 changed files with 569 additions and 1476 deletions

View file

@ -36,7 +36,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'java-kotlin' ] language: [ 'java' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support # Learn more about CodeQL language support at https://git.io/codeql-language-support
@ -46,7 +46,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -57,7 +57,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v3 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -71,4 +71,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v2

View file

@ -28,13 +28,9 @@ jobs:
with: with:
java-version: '11' java-version: '11'
distribution: 'temurin' distribution: 'temurin'
- name: Build and Check - name: Build, Check and Coverage
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: check jacocoRootReport
- name: Coveralls
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
env: env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
with: with:
arguments: coveralls arguments: check jacocoRootReport coveralls

86
.reuse/dep5 Normal file
View file

@ -0,0 +1,86 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: PGPainless
Upstream-Contact: Paul Schaub <info@pgpainless.org>
Source: https://pgpainless.org
# Sample paragraph, commented out:
#
# Files: src/*
# Copyright: $YEAR $NAME <$CONTACT>
# License: ...
# GitBlameIgnore
Files: .git-blame-ignore-revs
Copyright: 2023 Paul Schaub <info@pgpainless.org>
License: CC0-1.0
# Documentation
Files: docs/*
Copyright: 2022 Paul Schaub <info@pgpainless.org>
License: CC-BY-3.0
Files: .readthedocs.yaml
Copyright: 2022 Paul Schaub <info@pgpainless.org>
License: CC0-1.0
# Gradle build tool
Files: gradle*
Copyright: 2015 the original author or authors.
License: Apache-2.0
# Editorconfig
Files: .editorconfig
Copyright: Facebook
License: Apache-2.0
# PGPainless Logo
Files: assets/repository-open-graph.png
Copyright: 2021 Paul Schaub <info@pgpainless.org>
License: CC-BY-3.0
Files: assets/pgpainless.svg
Copyright: 2021 Paul Schaub <info@pgpainless.org>
License: CC-BY-3.0
Files: assets/logo.png
Copyright: 2022 Paul Schaub <info@pgpainless.org>
License: CC-BY-3.0
Files: assets/test_vectors/*
Copyright: 2018 Paul Schaub <info@pgpainless.org>
License: CC0-1.0
Files: pgpainless-core/src/test/resources/*
Copyright: 2020 Paul Schaub <info@pgpainless.org>
License: CC0-1.0
Files: audit/*
Copyright: 2021 Paul Schaub <info@pgpainless.org>
License: CC0-1.0
# GH Pages
Files: CNAME
Copyright: 2022 Paul Schaub <info@pgpainless.org>
License: CC0-1.0
Files: _config.yml
Copyright: 2022 Paul Schaub <info@pgpainless.org>
License: CC0-1.0
Files: _layouts/*
Copyright: 2022 Paul Schaub <info@pgpainless.org>, 2017 Steve Smith
License: CC-BY-SA-3.0
# Man Pages
Files: pgpainless-cli/rewriteManPages.sh
Copyright: 2022 Paul Schaub <info@pgpainless.org>
License: Apache-2.0
Files: pgpainless-cli/packaging/man/*
Copyright: 2022 Paul Schaub <info@pgpainless.org>
License: Apache-2.0
# Github Issue Templates
Files: .github/ISSUE_TEMPLATE/*
Copyright: 2024 Paul Schaub <info@pgpainless.org>
License: CC0-1.0

View file

@ -5,33 +5,6 @@ SPDX-License-Identifier: CC0-1.0
# PGPainless Changelog # PGPainless Changelog
## 1.7.7-SNAPSHOT
- Bump `bcpg-jdk8on` to `1.81`
- Bump `bcprov-jdk18on` to `1.81`
## 1.7.6
- Fix `RevocationSignatureBuilder` properly calculating third-party signatures of type `KeyRevocation` (delegation revocations)
- Enable support for native images
- Re-enable shadow plugin and build fat-jar
## 1.7.5
- Actually attempt to fix Kotlin desugaring.
- Bump javaSourceCompatibility and javaTargetCompatibility to 11
- Bump gradle-wrapper to 8.8
## 1.7.4
- Fix proper Kotlin desugaring for Java 8
## 1.7.3
- Bump `bcpg-jdk8on` to `1.80`
- Bump `bcprov-jdk18on` to `1.80`
- Add dependency on `bcutil-jdk18on` as a workaround
- Ignore unknown type signatures on certificates
- Fix typo on signature creation bounds check (thanks @elduffy)
- Fix superfluous newline added in CRLF encoding (thanks @bjansen)
- Bump `sop-java` to `1.10.0`
- SOP inline-sign: Do not apply compression
## 1.7.2 ## 1.7.2
- Fix bug in `KeyRingInfo.lastModified` (thanks to @Jerbell, @sosnovsky for reporting) - Fix bug in `KeyRingInfo.lastModified` (thanks to @Jerbell, @sosnovsky for reporting)
- Bump `sop-java` to `10.0.3` - Bump `sop-java` to `10.0.3`

View file

@ -164,7 +164,7 @@ This behaviour can be modified though using the `Policy` class.
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(encryptedInputStream) .onInputStream(encryptedInputStream)
.withOptions(new ConsumerOptions() .withOptions(new ConsumerOptions()
.addDecryptionKey(bobSecKeys, secretKeyProtector) .addMessagePassphrase(bobSecKeys, secretKeyProtector)
.addVerificationCert(alicePubKeys) .addVerificationCert(alicePubKeys)
); );
@ -191,7 +191,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'org.pgpainless:pgpainless-core:1.7.6' implementation 'org.pgpainless:pgpainless-core:1.7.2'
} }
``` ```
@ -222,6 +222,9 @@ Parts of PGPainless development ([project page](https://nlnet.nl/project/PGPainl
NGI Assure is made possible with financial support from the [European Commission](https://ec.europa.eu/)'s [Next Generation Internet](https://ngi.eu/) programme, under the aegis of [DG Communications Networks, Content and Technology](https://ec.europa.eu/info/departments/communications-networks-content-and-technology_en). NGI Assure is made possible with financial support from the [European Commission](https://ec.europa.eu/)'s [Next Generation Internet](https://ngi.eu/) programme, under the aegis of [DG Communications Networks, Content and Technology](https://ec.europa.eu/info/departments/communications-networks-content-and-technology_en).
[![NGI Assure Logo](https://blog.jabberhead.tk/wp-content/uploads/2022/05/NGIAssure_tag.svg)](https://nlnet.nl/assure/) [![NGI Assure Logo](https://blog.jabberhead.tk/wp-content/uploads/2022/05/NGIAssure_tag.svg)](https://nlnet.nl/assure/)
Thanks to [YourKit](https://www.yourkit.com/) for providing a free license of the [YourKit Java Profiler](https://www.yourkit.com/java/profiler/) to support PGPainless Development!
[![YourKit Logo](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com/)
Big thank you also to those who decided to support the work by donating! Big thank you also to those who decided to support the work by donating!
Notably @msfjarvis Notably @msfjarvis

View file

@ -1,118 +0,0 @@
version = 1
SPDX-PackageName = "PGPainless"
SPDX-PackageSupplier = "Paul Schaub <info@pgpainless.org>"
SPDX-PackageDownloadLocation = "https://pgpainless.org"
[[annotations]]
path = "REUSE.toml"
precedence = "aggregate"
SPDX-FileCopyrightText = "2025 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ".git-blame-ignore-revs"
precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "docs/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC-BY-3.0"
[[annotations]]
path = ".readthedocs.yaml"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "gradle**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2015 the original author or authors."
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = ".editorconfig"
precedence = "aggregate"
SPDX-FileCopyrightText = "Facebook"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "assets/repository-open-graph.png"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC-BY-3.0"
[[annotations]]
path = "assets/pgpainless.svg"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC-BY-3.0"
[[annotations]]
path = "assets/logo.png"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC-BY-3.0"
[[annotations]]
path = "assets/test_vectors/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2018 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "pgpainless-core/src/test/resources/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "audit/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "CNAME"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "_config.yml"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "_layouts/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>, 2017 Steve Smith"
SPDX-License-Identifier = "CC-BY-SA-3.0"
[[annotations]]
path = "pgpainless-cli/src/main/resources/META-INF/native-image/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2025 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "pgpainless-cli/rewriteManPages.sh"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "pgpainless-cli/packaging/man/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = ".github/ISSUE_TEMPLATE/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2024 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"

View file

@ -33,17 +33,24 @@ allprojects {
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'com.diffplug.spotless' apply plugin: 'com.diffplug.spotless'
java {
targetCompatibility = JavaVersion.VERSION_1_8
}
compileJava {
options.release = 8
}
// Only generate jar for submodules // Only generate jar for submodules
// without this we would generate an empty pgpainless.jar for the project root // without this we would generate an empty pgpainless.jar for the project root
// https://stackoverflow.com/a/25445035 // https://stackoverflow.com/a/25445035
jar { jar {
reproducibleFileOrder = true
onlyIf { !sourceSets.main.allSource.files.isEmpty() } onlyIf { !sourceSets.main.allSource.files.isEmpty() }
} }
// checkstyle // checkstyle
checkstyle { checkstyle {
toolVersion = '10.25.0' toolVersion = '10.12.1'
} }
spotless { spotless {
@ -56,6 +63,8 @@ allprojects {
description = "Simple to use OpenPGP API for Java based on Bouncycastle" description = "Simple to use OpenPGP API for Java based on Bouncycastle"
version = shortVersion version = shortVersion
sourceCompatibility = javaSourceCompatibility
repositories { repositories {
mavenCentral() mavenCentral()
mavenLocal() mavenLocal()
@ -70,10 +79,6 @@ allprojects {
fileMode = 0644 fileMode = 0644
} }
kotlin {
jvmToolchain(javaSourceCompatibility)
}
// Compatibility of default implementations in kotlin interfaces with Java implementations. // Compatibility of default implementations in kotlin interfaces with Java implementations.
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions { kotlinOptions {
@ -114,7 +119,7 @@ allprojects {
sourceDirectories.setFrom(project.files(sourceSets.main.allSource.srcDirs)) sourceDirectories.setFrom(project.files(sourceSets.main.allSource.srcDirs))
classDirectories.setFrom(project.files(sourceSets.main.output)) classDirectories.setFrom(project.files(sourceSets.main.output))
reports { reports {
xml.required = true xml.enabled true
} }
} }
@ -132,15 +137,15 @@ subprojects {
apply plugin: 'signing' apply plugin: 'signing'
task sourcesJar(type: Jar, dependsOn: classes) { task sourcesJar(type: Jar, dependsOn: classes) {
archiveClassifier = 'sources' classifier = 'sources'
from sourceSets.main.allSource from sourceSets.main.allSource
} }
task javadocJar(type: Jar, dependsOn: javadoc) { task javadocJar(type: Jar, dependsOn: javadoc) {
archiveClassifier = 'javadoc' classifier = 'javadoc'
from javadoc.destinationDir from javadoc.destinationDir
} }
task testsJar(type: Jar, dependsOn: testClasses) { task testsJar(type: Jar, dependsOn: testClasses) {
archiveClassifier = 'tests' classifier = 'tests'
from sourceSets.test.output from sourceSets.test.output
} }
@ -237,7 +242,7 @@ task jacocoRootReport(type: JacocoReport) {
classDirectories.setFrom(files(subprojects.sourceSets.main.output)) classDirectories.setFrom(files(subprojects.sourceSets.main.output))
executionData.setFrom(files(subprojects.jacocoTestReport.executionData)) executionData.setFrom(files(subprojects.jacocoTestReport.executionData))
reports { reports {
xml.required = true xml.enabled true
xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml") xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml")
} }
// We could remove the following setOnlyIf line, but then // We could remove the following setOnlyIf line, but then
@ -248,6 +253,10 @@ task jacocoRootReport(type: JacocoReport) {
} }
task javadocAll(type: Javadoc) { task javadocAll(type: Javadoc) {
def currentJavaVersion = JavaVersion.current()
if (currentJavaVersion.compareTo(JavaVersion.VERSION_1_9) >= 0) {
options.addStringOption("-release", "8");
}
source subprojects.collect {project -> source subprojects.collect {project ->
project.sourceSets.main.allJava } project.sourceSets.main.allJava }
destinationDir = new File(buildDir, 'javadoc') destinationDir = new File(buildDir, 'javadoc')
@ -261,6 +270,18 @@ task javadocAll(type: Javadoc) {
] as String[] ] as String[]
} }
if (JavaVersion.current().isJava8Compatible()) {
tasks.withType(Javadoc) {
// The '-quiet' as second argument is actually a hack,
// since the one paramater addStringOption doesn't seem to
// work, we extra add '-quiet', which is added anyway by
// gradle. See https://github.com/gradle/gradle/issues/2354
// See JDK-8200363 (https://bugs.openjdk.java.net/browse/JDK-8200363)
// for information about the -Xwerror option.
options.addStringOption('Xwerror', '-quiet')
}
}
/** /**
* Fetch sha256 checksums of artifacts published to maven central. * Fetch sha256 checksums of artifacts published to maven central.
* *
@ -270,13 +291,34 @@ task mavenCentralChecksums() {
description 'Fetch and display checksums for artifacts published to Maven Central' description 'Fetch and display checksums for artifacts published to Maven Central'
String ver = project.hasProperty('release') ? release : shortVersion String ver = project.hasProperty('release') ? release : shortVersion
doLast { doLast {
for (Project p : rootProject.subprojects) { Process p = "curl -f https://repo1.maven.org/maven2/org/pgpainless/pgpainless-core/${ver}/pgpainless-core-${ver}.jar.sha256".execute()
String url = "https://repo1.maven.org/maven2/org/pgpainless/${p.name}/${ver}/${p.name}-${ver}.jar.sha256" if (p.waitFor() == 0) {
Process fetch = "curl -f $url".execute() print p.text.trim()
if (fetch.waitFor() == 0) { println " pgpainless-core/build/libs/pgpainless-core-${ver}.jar"
print fetch.text.trim() }
println " ${p.name}/build/libs/${p.name}-${ver}.jar"
} p = "curl -f https://repo1.maven.org/maven2/org/pgpainless/pgpainless-sop/${ver}/pgpainless-sop-${ver}.jar.sha256".execute()
if (p.waitFor() == 0) {
print p.text.trim()
println " pgpainless-sop/build/libs/pgpainless-sop-${ver}.jar"
}
p = "curl -f https://repo1.maven.org/maven2/org/pgpainless/pgpainless-cli/${ver}/pgpainless-cli-${ver}-all.jar.sha256".execute()
if (p.waitFor() == 0) {
print p.text.trim()
println " pgpainless-cli/build/libs/pgpainless-cli-${ver}-all.jar"
}
p = "curl -f https://repo1.maven.org/maven2/org/pgpainless/pgpainless-cli/${ver}/pgpainless-cli-${ver}.jar.sha256".execute()
if (p.waitFor() == 0) {
print p.text.trim()
println " pgpainless-cli/build/libs/pgpainless-cli-${ver}.jar"
}
p = "curl -f https://repo1.maven.org/maven2/org/pgpainless/hsregex/${ver}/hsregex-${ver}.jar.sha256".execute()
if (p.waitFor() == 0) {
print p.text.trim()
println " hsregex/build/libs/hsregex-${ver}.jar"
} }
} }
} }

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -4,13 +4,27 @@
plugins { plugins {
id 'application' id 'application'
id 'org.graalvm.buildtools.native' version '0.10.6' id "com.github.johnrengelman.shadow" version "6.1.0"
id 'com.gradleup.shadow' version '8.3.6' }
def generatedVersionDir = "${buildDir}/generated-version"
sourceSets {
main {
output.dir(generatedVersionDir, builtBy: 'generateVersionProperties')
}
} }
graalvmNative { task generateVersionProperties {
toolchainDetection = true doLast {
def propertiesFile = file "$generatedVersionDir/version.properties"
propertiesFile.parentFile.mkdirs()
propertiesFile.createNewFile()
// Instead of using a Properties object here, we directly write to the file
// since Properties adds a timestamp, ruining reproducibility
propertiesFile.write("version="+rootProject.version.toString())
}
} }
processResources.dependsOn generateVersionProperties
dependencies { dependencies {
@ -18,12 +32,13 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// https://todd.ginsberg.com/post/testing-system-exit/
testImplementation 'com.ginsberg:junit5-system-exit:1.1.2'
// implementation "ch.qos.logback:logback-core:1.2.6" // implementation "ch.qos.logback:logback-core:1.2.6"
// We want logback logging in tests and in the app // We want logback logging in tests and in the app
testImplementation "ch.qos.logback:logback-classic:$logbackVersion" testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
// implementation "ch.qos.logback:logback-classic:$logbackVersion" implementation "ch.qos.logback:logback-classic:$logbackVersion"
implementation "org.slf4j:slf4j-nop:$slf4jVersion"
implementation(project(":pgpainless-sop")) implementation(project(":pgpainless-sop"))
implementation "org.pgpainless:sop-java-picocli:$sopJavaVersion" implementation "org.pgpainless:sop-java-picocli:$sopJavaVersion"
@ -37,6 +52,22 @@ mainClassName = 'org.pgpainless.cli.PGPainlessCLI'
application { application {
mainClass = mainClassName mainClass = mainClassName
} }
/**
jar {
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
manifest {
attributes 'Main-Class': "$mainClassName"
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
} {
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
}
*/
run { run {
// https://stackoverflow.com/questions/59445306/pipe-into-gradle-run // https://stackoverflow.com/questions/59445306/pipe-into-gradle-run
@ -46,3 +77,5 @@ run {
args Eval.me(appArgs) args Eval.me(appArgs)
} }
} }
// tasks."jar".dependsOn(":pgpainless-core:assemble", ":pgpainless-sop:assemble")

View file

@ -14,10 +14,6 @@ import sop.cli.picocli.SopCLI;
public class PGPainlessCLI { public class PGPainlessCLI {
static { static {
// Prevent slf4j initialization logging
// https://github.com/qos-ch/slf4j/issues/422#issuecomment-2277280185
System.setProperty("slf4j.internal.verbosity", "WARN");
SopCLI.EXECUTABLE_NAME = "pgpainless-cli"; SopCLI.EXECUTABLE_NAME = "pgpainless-cli";
SopCLI.setSopInstance(new SOPImpl()); SopCLI.setSopInstance(new SOPImpl());
} }

View file

@ -1,7 +0,0 @@
[
{
"type":"agent-extracted",
"classes":[
]
}
]

View file

@ -1,891 +0,0 @@
[
{
"name":"[Ljava.lang.Object;"
},
{
"name":"ch.qos.logback.classic.encoder.PatternLayoutEncoder",
"queryAllPublicMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"ch.qos.logback.classic.joran.SerializedModelConfigurator",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"ch.qos.logback.classic.util.DefaultJoranConfigurator",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"ch.qos.logback.core.ConsoleAppender",
"queryAllPublicMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setTarget","parameterTypes":["java.lang.String"] }]
},
{
"name":"ch.qos.logback.core.OutputStreamAppender",
"methods":[{"name":"setEncoder","parameterTypes":["ch.qos.logback.core.encoder.Encoder"] }]
},
{
"name":"ch.qos.logback.core.encoder.Encoder",
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
},
{
"name":"ch.qos.logback.core.encoder.LayoutWrappingEncoder",
"methods":[{"name":"setParent","parameterTypes":["ch.qos.logback.core.spi.ContextAware"] }]
},
{
"name":"ch.qos.logback.core.pattern.PatternLayoutEncoderBase",
"methods":[{"name":"setPattern","parameterTypes":["java.lang.String"] }]
},
{
"name":"ch.qos.logback.core.spi.ContextAware",
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
},
{
"name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"groovy.lang.Closure"
},
{
"name":"java.io.FilePermission"
},
{
"name":"java.lang.Enum"
},
{
"name":"java.lang.Object",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"java.lang.RuntimePermission"
},
{
"name":"java.lang.System",
"methods":[{"name":"console","parameterTypes":[] }]
},
{
"name":"java.lang.invoke.MethodHandle"
},
{
"name":"java.net.NetPermission"
},
{
"name":"java.net.SocketPermission"
},
{
"name":"java.net.URLPermission",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }]
},
{
"name":"java.nio.channels.SelectionKey",
"fields":[{"name":"attachment"}]
},
{
"name":"java.nio.file.Path"
},
{
"name":"java.nio.file.Paths",
"methods":[{"name":"get","parameterTypes":["java.lang.String","java.lang.String[]"] }]
},
{
"name":"java.security.AllPermission"
},
{
"name":"java.security.MessageDigestSpi"
},
{
"name":"java.security.SecureRandomParameters"
},
{
"name":"java.security.SecurityPermission"
},
{
"name":"java.security.cert.PKIXRevocationChecker"
},
{
"name":"java.sql.Connection"
},
{
"name":"java.sql.Driver"
},
{
"name":"java.sql.DriverManager",
"methods":[{"name":"getConnection","parameterTypes":["java.lang.String"] }, {"name":"getDriver","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.sql.Time",
"methods":[{"name":"<init>","parameterTypes":["long"] }]
},
{
"name":"java.sql.Timestamp",
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.time.Duration",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.Instant",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.LocalDate",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.LocalDateTime",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.LocalTime",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.MonthDay",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.OffsetDateTime",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.OffsetTime",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.Period",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.Year",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.YearMonth",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.ZoneId",
"methods":[{"name":"of","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.time.ZoneOffset",
"methods":[{"name":"of","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.time.ZonedDateTime",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.util.HashSet"
},
{
"name":"java.util.LinkedHashSet"
},
{
"name":"java.util.PropertyPermission"
},
{
"name":"java.util.concurrent.ArrayBlockingQueue"
},
{
"name":"java.util.concurrent.atomic.AtomicReference",
"fields":[{"name":"value"}]
},
{
"name":"java.util.concurrent.locks.AbstractOwnableSynchronizer"
},
{
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer"
},
{
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject"
},
{
"name":"java.util.concurrent.locks.ReentrantLock"
},
{
"name":"java.util.concurrent.locks.ReentrantLock$NonfairSync"
},
{
"name":"java.util.concurrent.locks.ReentrantLock$Sync"
},
{
"name":"javax.smartcardio.CardPermission"
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.COMPOSITE$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.CONTEXT$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.CompositeSignatures$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.DH$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.DSA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.DSTU4145$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.Dilithium$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.EC$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.ECGOST$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.EXTERNAL$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.EdEC$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.ElGamal$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.Falcon$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.GM$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.GOST$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.IES$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.LMS$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.MLDSA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.MLKEM$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.NTRU$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.NoSig$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.SLHDSA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.SPHINCSPlus$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.X509$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyPairGeneratorSpi$EdDSA",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyPairGeneratorSpi$XDH",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyPairGeneratorSpi",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Blake2b$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Blake2s$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Blake3$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.DSTU7564$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.GOST3411$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Haraka$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Keccak$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.MD2$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.MD4$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.MD5$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD128$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD160$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD256$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD320$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA1$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA224$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA256$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA3$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA384$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA512$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SM3$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Skein$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Tiger$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Whirlpool$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.drbg.DRBG$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.keystore.BC$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.keystore.BCFKS$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.keystore.PKCS12$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.AES$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.ARC4$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.ARIA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Blowfish$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.CAST5$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.CAST6$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Camellia$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.ChaCha$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.DES$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.DSTU7624$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.GOST28147$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.GOST3412_2015$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Grain128$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Grainv1$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.HC128$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.HC256$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.IDEA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Noekeon$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.OpenSSLPBKDF$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF1$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Poly1305$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.RC2$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.RC5$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.RC6$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Rijndael$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SCRYPT$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SEED$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SM4$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Salsa20$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Serpent$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Shacal2$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SipHash$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SipHash128$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Skipjack$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.TEA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.TLSKDF$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Threefish$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Twofish$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.VMPC$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.VMPCKSA3$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.XSalsa20$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.XTEA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Zuc$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.ExitCodeTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"successfulExecutionDoesNotTerminateJVM","parameterTypes":[] }, {"name":"testCommandWithUnknownOption_37","parameterTypes":[] }, {"name":"testUnknownCommand_69","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.TestUtils",
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true
},
{
"name":"org.pgpainless.cli.commands.ArmorCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"armorAlreadyArmoredDataIsIdempotent","parameterTypes":[] }, {"name":"armorMessage","parameterTypes":[] }, {"name":"armorPublicKey","parameterTypes":[] }, {"name":"armorSecretKey","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.CLITest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"methods":[{"name":"cleanup","parameterTypes":[] }, {"name":"setup","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.DearmorCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"dearmorBrokenArmoredKeyFails","parameterTypes":[] }, {"name":"dearmorCertificate","parameterTypes":[] }, {"name":"dearmorGarbageEmitsEmpty","parameterTypes":[] }, {"name":"dearmorMessage","parameterTypes":[] }, {"name":"dearmorSecretKey","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.ExtractCertCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"extractCertFromGarbageFails","parameterTypes":[] }, {"name":"testExtractCert","parameterTypes":[] }, {"name":"testExtractCertFromCertFails","parameterTypes":[] }, {"name":"testExtractCertUnarmored","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.GenerateKeyCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testGenerateBinaryKey","parameterTypes":[] }, {"name":"testGenerateKey","parameterTypes":[] }, {"name":"testGenerateKeyWithMultipleUserIds","parameterTypes":[] }, {"name":"testGeneratePasswordProtectedKey_missingPasswordFile","parameterTypes":[] }, {"name":"testPasswordProtectedKey","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.InlineDetachCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"detachInbandSignatureAndMessage","parameterTypes":[] }, {"name":"detachInbandSignatureAndMessageNoArmor","parameterTypes":[] }, {"name":"detachMissingSignaturesFromCleartextSignedMessageFails","parameterTypes":[] }, {"name":"detachNonOpenPgpDataFails","parameterTypes":[] }, {"name":"existingSignatureOutCausesException","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.ListProfilesCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"listProfileOfGenerateKey","parameterTypes":[] }, {"name":"listProfilesOfEncrypt","parameterTypes":[] }, {"name":"listProfilesWithoutCommand","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.RoundTripEncryptDecryptCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"decryptGarbageFails","parameterTypes":[] }, {"name":"decryptMalformedMessageYieldsBadData","parameterTypes":[] }, {"name":"decryptMessageWithSessionKey","parameterTypes":[] }, {"name":"decryptMessageWithWrongKeyFails","parameterTypes":[] }, {"name":"decryptWithPasswordWithPendingWhitespaceWorks","parameterTypes":[] }, {"name":"decryptWithWhitespacePasswordWorks","parameterTypes":[] }, {"name":"decrypt_verifyWithGarbageCertFails","parameterTypes":[] }, {"name":"decrypt_withGarbageKeyFails","parameterTypes":[] }, {"name":"encryptAndDecryptAMessage","parameterTypes":[] }, {"name":"encryptAndDecryptMessageWithPassphrase","parameterTypes":[] }, {"name":"encryptWithGarbageCertFails","parameterTypes":[] }, {"name":"encryptWithPasswordADecryptWithPasswordBFails","parameterTypes":[] }, {"name":"encryptWithProtectedKey_wrongPassphraseFails","parameterTypes":[] }, {"name":"encryptWithTrailingWhitespaceDecryptWithoutWorks","parameterTypes":[] }, {"name":"encrypt_signWithGarbageKeyFails","parameterTypes":[] }, {"name":"testDecryptVerifyOut_withoutVerifyWithFails","parameterTypes":[] }, {"name":"testDecryptWithSessionKeyVerifyWithYieldsExpectedVerifications","parameterTypes":[] }, {"name":"testDecryptWithoutDecryptionOptionFails","parameterTypes":[] }, {"name":"testEncryptDecryptRoundTripWithPasswordProtectedKey","parameterTypes":[] }, {"name":"testEncryptDecryptWithFreshRSAKey","parameterTypes":[] }, {"name":"testEncryptWithIncapableCert","parameterTypes":[] }, {"name":"testEncrypt_SignWithCertFails","parameterTypes":[] }, {"name":"testMissingArgumentsIfNoArgsSupplied","parameterTypes":[] }, {"name":"testSessionKeyOutWritesSessionKeyOut","parameterTypes":[] }, {"name":"testSignWithIncapableKey","parameterTypes":[] }, {"name":"testVerificationsOutAlreadyExistFails","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.RoundTripInlineSignInlineVerifyCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"cannotVerifyEncryptedMessage","parameterTypes":[] }, {"name":"cannotVerifyMalformedMessage","parameterTypes":[] }, {"name":"createAndVerifyCleartextSignedMessage","parameterTypes":[] }, {"name":"createAndVerifyMultiKeyBinarySignedMessage","parameterTypes":[] }, {"name":"createAndVerifyTextSignedMessage","parameterTypes":[] }, {"name":"createCleartextSignedMessage","parameterTypes":[] }, {"name":"createMalformedMessage","parameterTypes":[] }, {"name":"createSignedMessageWithKeyAAndVerifyWithKeyBFails","parameterTypes":[] }, {"name":"createTextSignedMessageInlineDetachAndDetachedVerify","parameterTypes":[] }, {"name":"signWithProtectedKeyWithWrongPassphraseFails","parameterTypes":[] }, {"name":"testInlineSignWithMissingSecretKeysFails","parameterTypes":[] }, {"name":"testUnlockKeyWithOneOfMultiplePasswords","parameterTypes":[] }, {"name":"verifyPrependedSignedMessage","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.RoundTripInlineSignVerifyCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"encryptAndDecryptAMessage","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.RoundTripSignVerifyCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"createArmoredSignature","parameterTypes":[] }, {"name":"createUnarmoredSignature","parameterTypes":[] }, {"name":"signWithProtectedKey","parameterTypes":[] }, {"name":"signWithProtectedKey_missingPassphraseFails","parameterTypes":[] }, {"name":"signWithProtectedKey_wrongPassphraseFails","parameterTypes":[] }, {"name":"testNotAfter","parameterTypes":[] }, {"name":"testNotBefore","parameterTypes":[] }, {"name":"testSignWithIncapableKey","parameterTypes":[] }, {"name":"testSignatureCreationAndVerification","parameterTypes":[] }, {"name":"unarmorArmoredSigAndVerify","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.VersionCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testExtendedVersion","parameterTypes":[] }, {"name":"testGetBackendVersion","parameterTypes":[] }, {"name":"testSopSpecVersion","parameterTypes":[] }, {"name":"testVersion","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.misc.SignUsingPublicKeyBehaviorTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testSignatureCreationAndVerification","parameterTypes":[] }]
},
{
"name":"picocli.AutoComplete$GenerateCompletion",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"picocli.CommandLine$AutoHelpMixin",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"picocli.CommandLine$HelpCommand",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"sop.cli.picocli.SopCLI",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.SopCLI$InitLocale",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"sop.cli.picocli.commands.AbstractSopCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"sop.cli.picocli.commands.ArmorCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.ChangeKeyPasswordCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"sop.cli.picocli.commands.DearmorCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.DecryptCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.EncryptCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.ExtractCertCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.GenerateKeyCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.InlineDetachCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.InlineSignCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.InlineVerifyCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.ListProfilesCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.RevokeKeyCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"sop.cli.picocli.commands.SignCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.VerifyCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.VersionCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.VersionCmd$Exclusive",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.NativePRNG",
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
},
{
"name":"sun.security.provider.SHA",
"methods":[{"name":"<init>","parameterTypes":[] }]
}
]

View file

@ -1,93 +0,0 @@
{
"resources":{
"includes":[{
"pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E"
}, {
"pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E"
}, {
"pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E"
}, {
"pattern":"\\QMETA-INF/services/org.junit.platform.engine.TestEngine\\E"
}, {
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener\\E"
}, {
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.LauncherSessionListener\\E"
}, {
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.PostDiscoveryFilter\\E"
}, {
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.TestExecutionListener\\E"
}, {
"pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
}, {
"pattern":"\\Qjunit-platform.properties\\E"
}, {
"pattern":"\\Qlogback-test.scmo\\E"
}, {
"pattern":"\\Qlogback-test.xml\\E"
}, {
"pattern":"\\Qlogback.scmo\\E"
}, {
"pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"
}, {
"pattern":"\\Qpgpainless-sop.properties\\E"
}, {
"pattern":"\\Qsop-java-version.properties\\E"
}, {
"pattern":"java.base:\\Qsun/text/resources/LineBreakIteratorData\\E"
}]},
"bundles":[{
"name":"msg_armor",
"locales":["de", "und"]
}, {
"name":"msg_change-key-password",
"locales":["de", "und"]
}, {
"name":"msg_dearmor",
"locales":["de", "und"]
}, {
"name":"msg_decrypt",
"locales":["de", "und"]
}, {
"name":"msg_detached-sign",
"locales":["de", "und"]
}, {
"name":"msg_detached-verify",
"locales":["de", "und"]
}, {
"name":"msg_encrypt",
"locales":["de", "und"]
}, {
"name":"msg_extract-cert",
"locales":["de", "und"]
}, {
"name":"msg_generate-key",
"locales":["de", "und"]
}, {
"name":"msg_inline-detach",
"locales":["de", "und"]
}, {
"name":"msg_inline-sign",
"locales":["de", "und"]
}, {
"name":"msg_inline-verify",
"locales":["de", "und"]
}, {
"name":"msg_list-profiles",
"locales":["de", "und"]
}, {
"name":"msg_revoke-key",
"locales":["de", "und"]
}, {
"name":"msg_sop",
"locales":["de", "und"]
}, {
"name":"msg_version",
"locales":["de", "und"]
}]
}

View file

@ -1,41 +0,0 @@
{
"types":[
{
"name":"java.lang.Enum"
},
{
"name":"java.lang.Object[]"
},
{
"name":"java.util.HashSet"
},
{
"name":"java.util.LinkedHashSet"
},
{
"name":"java.util.concurrent.ArrayBlockingQueue"
},
{
"name":"java.util.concurrent.locks.AbstractOwnableSynchronizer"
},
{
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer"
},
{
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject"
},
{
"name":"java.util.concurrent.locks.ReentrantLock"
},
{
"name":"java.util.concurrent.locks.ReentrantLock$NonfairSync"
},
{
"name":"java.util.concurrent.locks.ReentrantLock$Sync"
}
],
"lambdaCapturingTypes":[
],
"proxies":[
]
}

View file

@ -5,5 +5,22 @@ SPDX-License-Identifier: Apache-2.0
--> -->
<configuration debug="false"> <configuration debug="false">
<statusListener class="ch.qos.logback.core.status.NopStatusListener" /> <appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="error">
<appender-ref ref="STDERR" />
</root>
</configuration> </configuration>

View file

@ -4,35 +4,28 @@
package org.pgpainless.cli; package org.pgpainless.cli;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.pgpainless.cli.commands.CLITest;
import org.slf4j.LoggerFactory;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
import java.io.IOException; public class ExitCodeTest {
import static org.junit.jupiter.api.Assertions.assertEquals; @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedSubcommand.EXIT_CODE)
public class ExitCodeTest extends CLITest { public void testUnknownCommand_69() {
PGPainlessCLI.main(new String[] {"generate-kex"});
public ExitCodeTest() {
super(LoggerFactory.getLogger(ExitCodeTest.class));
} }
@Test @Test
public void testUnknownCommand_69() throws IOException { @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
assertEquals(SOPGPException.UnsupportedSubcommand.EXIT_CODE, public void testCommandWithUnknownOption_37() {
executeCommand("unsupported-subcommand")); PGPainlessCLI.main(new String[] {"generate-key", "-k", "\"k is unknown\""});
} }
@Test @Test
public void testCommandWithUnknownOption_37() throws IOException { @FailOnSystemExit
assertEquals(SOPGPException.UnsupportedOption.EXIT_CODE, public void successfulExecutionDoesNotTerminateJVM() {
executeCommand("generate-key", "-k", "\"k is unknown\"")); PGPainlessCLI.main(new String[] {"version"});
}
@Test
public void successfulExecutionDoesNotTerminateJVM() throws IOException {
assertSuccess(executeCommand("version"));
} }
} }

View file

@ -14,6 +14,7 @@ import java.io.IOException;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
@ -82,6 +83,7 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest {
"-----END PGP PUBLIC KEY BLOCK-----"; "-----END PGP PUBLIC KEY BLOCK-----";
@Test @Test
@FailOnSystemExit
public void encryptAndDecryptAMessage() throws IOException { public void encryptAndDecryptAMessage() throws IOException {
// Juliets key and cert // Juliets key and cert
File julietKeyFile = pipeStdoutToFile("juliet.key"); File julietKeyFile = pipeStdoutToFile("juliet.key");

View file

@ -138,10 +138,6 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
"\n" + "\n" +
"There is only one Lord of the Keys, only one who can bend them to his will. And he does not share power."; "There is only one Lord of the Keys, only one who can bend them to his will. And he does not share power.";
private static final String MESSAGE_CRLF = "One does not simply use OpenPGP!\r\n" +
"\r\n" +
"There is only one Lord of the Keys, only one who can bend them to his will. And he does not share power.";
@Test @Test
public void createCleartextSignedMessage() throws IOException { public void createCleartextSignedMessage() throws IOException {
File key = writeFile("key.asc", KEY_1); File key = writeFile("key.asc", KEY_1);
@ -157,7 +153,7 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
String cleartextSigned = ciphertextOut.toString(); String cleartextSigned = ciphertextOut.toString();
assertTrue(cleartextSigned.startsWith("-----BEGIN PGP SIGNED MESSAGE-----\n" + assertTrue(cleartextSigned.startsWith("-----BEGIN PGP SIGNED MESSAGE-----\n" +
"Hash: ")); "Hash: "));
assertTrue(cleartextSigned.contains(MESSAGE_CRLF)); assertTrue(cleartextSigned.contains(MESSAGE));
assertTrue(cleartextSigned.contains("\n-----BEGIN PGP SIGNATURE-----\n")); assertTrue(cleartextSigned.contains("\n-----BEGIN PGP SIGNATURE-----\n"));
assertTrue(cleartextSigned.endsWith("-----END PGP SIGNATURE-----\n")); assertTrue(cleartextSigned.endsWith("-----END PGP SIGNATURE-----\n"));
} }
@ -207,7 +203,7 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
"--verifications-out", verifications.getAbsolutePath(), "--verifications-out", verifications.getAbsolutePath(),
cert.getAbsolutePath())); cert.getAbsolutePath()));
assertEquals(MESSAGE_CRLF, plaintextOut.toString()); assertEquals(MESSAGE, plaintextOut.toString());
String verificationString = readStringFromFile(verifications); String verificationString = readStringFromFile(verifications);
assertTrue(verificationString.contains(CERT_1_SIGNING_KEY)); assertTrue(verificationString.contains(CERT_1_SIGNING_KEY));
} }

View file

@ -4,49 +4,104 @@
package org.pgpainless.cli.commands; package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory; import org.pgpainless.cli.PGPainlessCLI;
import org.pgpainless.cli.TestUtils;
public class RoundTripInlineSignVerifyCmdTest extends CLITest { public class RoundTripInlineSignVerifyCmdTest {
private static File tempDir;
private static PrintStream originalSout;
public RoundTripInlineSignVerifyCmdTest() { @BeforeAll
super(LoggerFactory.getLogger(RoundTripInlineSignVerifyCmdTest.class)); public static void prepare() throws IOException {
tempDir = TestUtils.createTempDirectory();
} }
@Test @Test
@FailOnSystemExit
public void encryptAndDecryptAMessage() throws IOException { public void encryptAndDecryptAMessage() throws IOException {
originalSout = System.out;
File sigmundKeyFile = new File(tempDir, "sigmund.key");
assertTrue(sigmundKeyFile.createNewFile());
File sigmundCertFile = new File(tempDir, "sigmund.cert");
assertTrue(sigmundCertFile.createNewFile());
File msgFile = new File(tempDir, "signed.asc");
assertTrue(msgFile.createNewFile());
File passwordFile = new File(tempDir, "password");
assertTrue(passwordFile.createNewFile());
// write password file // write password file
File password = writeFile("password", "sw0rdf1sh"); FileOutputStream passwordOut = new FileOutputStream(passwordFile);
passwordOut.write("sw0rdf1sh".getBytes(StandardCharsets.UTF_8));
passwordOut.close();
// generate key // generate key
File sigmundKey = pipeStdoutToFile("sigmund.key"); OutputStream sigmundKeyOut = new FileOutputStream(sigmundKeyFile);
assertSuccess(executeCommand("generate-key", "--with-key-password=" + password.getAbsolutePath(), System.setOut(new PrintStream(sigmundKeyOut));
"Sigmund Freud <sigmund@pgpainless.org>")); PGPainlessCLI.execute("generate-key",
"--with-key-password=" + passwordFile.getAbsolutePath(),
"Sigmund Freud <sigmund@pgpainless.org>");
sigmundKeyOut.close();
// extract cert // extract cert
File sigmundCert = pipeStdoutToFile("sigmund.cert"); FileInputStream sigmundKeyIn = new FileInputStream(sigmundKeyFile);
pipeFileToStdin(sigmundKey); System.setIn(sigmundKeyIn);
assertSuccess(executeCommand("extract-cert")); OutputStream sigmundCertOut = new FileOutputStream(sigmundCertFile);
System.setOut(new PrintStream(sigmundCertOut));
PGPainlessCLI.execute("extract-cert");
sigmundKeyIn.close();
sigmundCertOut.close();
// sign message // sign message
pipeBytesToStdin("Hello, World!\n".getBytes(StandardCharsets.UTF_8)); String msg = "Hello World!\n";
File signedMsg = pipeStdoutToFile("signed.asc"); ByteArrayInputStream msgIn = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8));
assertSuccess(executeCommand("inline-sign", "--with-key-password=" + password.getAbsolutePath(), System.setIn(msgIn);
sigmundKey.getAbsolutePath())); OutputStream msgAscOut = new FileOutputStream(msgFile);
System.setOut(new PrintStream(msgAscOut));
PGPainlessCLI.execute("inline-sign",
"--with-key-password=" + passwordFile.getAbsolutePath(),
sigmundKeyFile.getAbsolutePath());
msgAscOut.close();
// verify message File verifyFile = new File(tempDir, "verify.txt");
File verifyFile = nonExistentFile("verify.txt");
pipeFileToStdin(signedMsg);
assertSuccess(executeCommand("inline-verify", "--verifications-out", verifyFile.getAbsolutePath(),
sigmundCert.getAbsolutePath()));
String verifications = readStringFromFile(verifyFile); FileInputStream msgAscIn = new FileInputStream(msgFile);
assertFalse(verifications.trim().isEmpty()); System.setIn(msgAscIn);
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream pOut = new PrintStream(out);
System.setOut(pOut);
PGPainlessCLI.execute("inline-verify",
"--verifications-out", verifyFile.getAbsolutePath(),
sigmundCertFile.getAbsolutePath());
msgAscIn.close();
assertEquals(msg, out.toString());
}
@AfterAll
public static void after() {
System.setOut(originalSout);
// CHECKSTYLE:OFF
System.out.println(tempDir.getAbsolutePath());
// CHECKSTYLE:ON
} }
} }

View file

@ -4,18 +4,28 @@
package org.pgpainless.cli.misc; package org.pgpainless.cli.misc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.pgpainless.cli.commands.CLITest; import org.pgpainless.cli.PGPainlessCLI;
import org.slf4j.LoggerFactory; import org.pgpainless.cli.TestUtils;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
public class SignUsingPublicKeyBehaviorTest extends CLITest { public class SignUsingPublicKeyBehaviorTest {
public static final String KEY_THAT_IS_A_CERT = "" + public static final String KEY_THAT_IS_A_CERT = "" +
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
@ -79,24 +89,61 @@ public class SignUsingPublicKeyBehaviorTest extends CLITest {
"=oJQ2\n" + "=oJQ2\n" +
"-----END PGP PUBLIC KEY BLOCK-----"; "-----END PGP PUBLIC KEY BLOCK-----";
public SignUsingPublicKeyBehaviorTest() {
super(LoggerFactory.getLogger(SignUsingPublicKeyBehaviorTest.class)); private static File tempDir;
private static PrintStream originalSout;
@BeforeAll
public static void prepare() throws IOException {
tempDir = TestUtils.createTempDirectory();
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.KeyCannotSign.EXIT_CODE)
public void testSignatureCreationAndVerification() throws IOException { public void testSignatureCreationAndVerification() throws IOException {
originalSout = System.out;
InputStream originalIn = System.in;
// Write alice key to disc // Write alice key to disc
File aliceKeyFile = writeFile("alice.key", KEY_THAT_IS_A_CERT); File aliceKeyFile = new File(tempDir, "alice.key");
assertTrue(aliceKeyFile.createNewFile());
OutputStream aliceKeyOut = new FileOutputStream(aliceKeyFile);
Streams.pipeAll(new ByteArrayInputStream(KEY_THAT_IS_A_CERT.getBytes(StandardCharsets.UTF_8)), aliceKeyOut);
aliceKeyOut.close();
// Write alice pub key to disc
File aliceCertFile = new File(tempDir, "alice.pub");
assertTrue(aliceCertFile.createNewFile());
OutputStream aliceCertOut = new FileOutputStream(aliceCertFile);
Streams.pipeAll(new ByteArrayInputStream(KEY_THAT_IS_A_CERT.getBytes(StandardCharsets.UTF_8)), aliceCertOut);
aliceCertOut.close();
// Write test data to disc // Write test data to disc
File dataFile = writeFile("data", "If privacy is outlawed, only outlaws will have privacy.\n"); String data = "If privacy is outlawed, only outlaws will have privacy.\n";
File dataFile = new File(tempDir, "data");
assertTrue(dataFile.createNewFile());
FileOutputStream dataOut = new FileOutputStream(dataFile);
Streams.pipeAll(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), dataOut);
dataOut.close();
// Sign test data // Sign test data
File sigFile = pipeStdoutToFile("sig.asc"); FileInputStream dataIn = new FileInputStream(dataFile);
pipeFileToStdin(dataFile); System.setIn(dataIn);
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, File sigFile = new File(tempDir, "sig.asc");
executeCommand("sign", "--armor", aliceKeyFile.getAbsolutePath())); assertTrue(sigFile.createNewFile());
FileOutputStream sigOut = new FileOutputStream(sigFile);
System.setOut(new PrintStream(sigOut));
PGPainlessCLI.main(new String[] {"sign", "--armor", aliceKeyFile.getAbsolutePath()});
assertTrue(readStringFromFile(sigFile).trim().isEmpty()); System.setIn(originalIn);
}
@AfterAll
public static void after() {
System.setOut(originalSout);
// CHECKSTYLE:OFF
System.out.println(tempDir.getAbsolutePath());
// CHECKSTYLE:ON
} }
} }

View file

@ -22,7 +22,6 @@ dependencies {
// Bouncy Castle // Bouncy Castle
api "org.bouncycastle:bcprov-jdk18on:$bouncyCastleVersion" api "org.bouncycastle:bcprov-jdk18on:$bouncyCastleVersion"
api "org.bouncycastle:bcpg-jdk18on:$bouncyPgVersion" api "org.bouncycastle:bcpg-jdk18on:$bouncyPgVersion"
api "org.bouncycastle:bcutil-jdk18on:$bouncyCastleVersion"
// api(files("../libs/bcpg-jdk18on-1.70.jar")) // api(files("../libs/bcpg-jdk18on-1.70.jar"))
// @Nullable, @Nonnull annotations // @Nullable, @Nonnull annotations

View file

@ -32,7 +32,6 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.NoSuchElementException;
import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.BCPGInputStream;
import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPCompressedData;
@ -209,8 +208,8 @@ public class OpenPgpInputStream extends BufferedInputStream {
} }
try { try {
SignatureType.requireFromCode(sigType); SignatureType.valueOf(sigType);
} catch (NoSuchElementException e) { } catch (IllegalArgumentException e) {
return; return;
} }
@ -237,8 +236,8 @@ public class OpenPgpInputStream extends BufferedInputStream {
if (opsVersion == 3) { if (opsVersion == 3) {
int opsSigType = bcpgIn.read(); int opsSigType = bcpgIn.read();
try { try {
SignatureType.requireFromCode(opsSigType); SignatureType.valueOf(opsSigType);
} catch (NoSuchElementException e) { } catch (IllegalArgumentException e) {
return; return;
} }
int opsHashAlg = bcpgIn.read(); int opsHashAlg = bcpgIn.read();

View file

@ -28,14 +28,7 @@ public class SignatureValidationException extends PGPException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(rejections.size()).append(" rejected signatures:\n"); sb.append(rejections.size()).append(" rejected signatures:\n");
for (PGPSignature signature : rejections.keySet()) { for (PGPSignature signature : rejections.keySet()) {
String typeString; sb.append(SignatureType.valueOf(signature.getSignatureType())).append(' ')
SignatureType type = SignatureType.fromCode(signature.getSignatureType());
if (type == null) {
typeString = "0x" + Long.toHexString(signature.getSignatureType());
} else {
typeString = type.toString();
}
sb.append(typeString).append(' ')
.append(signature.getCreationTime()).append(": ") .append(signature.getCreationTime()).append(": ")
.append(rejections.get(signature).getMessage()).append('\n'); .append(rejections.get(signature).getMessage()).append('\n');
} }

View file

@ -34,11 +34,7 @@ public final class OpenPgpKeyAttributeUtil {
continue; continue;
} }
SignatureType signatureType = SignatureType.fromCode(signature.getSignatureType()); SignatureType signatureType = SignatureType.valueOf(signature.getSignatureType());
if (signatureType == null) {
// unknown signature type
continue;
}
if (signatureType == SignatureType.POSITIVE_CERTIFICATION if (signatureType == SignatureType.POSITIVE_CERTIFICATION
|| signatureType == SignatureType.GENERIC_CERTIFICATION) { || signatureType == SignatureType.GENERIC_CERTIFICATION) {
int[] hashAlgos = signature.getHashedSubPackets().getPreferredHashAlgorithms(); int[] hashAlgos = signature.getHashedSubPackets().getPreferredHashAlgorithms();
@ -75,8 +71,8 @@ public final class OpenPgpKeyAttributeUtil {
continue; continue;
} }
SignatureType signatureType = SignatureType.fromCode(signature.getSignatureType()); SignatureType signatureType = SignatureType.valueOf(signature.getSignatureType());
if (signatureType == null || signatureType != SignatureType.POSITIVE_CERTIFICATION if (signatureType != SignatureType.POSITIVE_CERTIFICATION
&& signatureType != SignatureType.GENERIC_CERTIFICATION) { && signatureType != SignatureType.GENERIC_CERTIFICATION) {
continue; continue;
} }

View file

@ -170,8 +170,7 @@ enum class SignatureType(val code: Int) {
@JvmStatic @JvmStatic
fun isRevocationSignature(signatureType: Int): Boolean { fun isRevocationSignature(signatureType: Int): Boolean {
val sigType = fromCode(signatureType) return isRevocationSignature(valueOf(signatureType))
return sigType?.let { isRevocationSignature(it) } ?: false
} }
@JvmStatic @JvmStatic

View file

@ -77,8 +77,7 @@ fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean = wasIssuedBy(OpenPgpFi
/** Return true, if this signature is a hard revocation. */ /** Return true, if this signature is a hard revocation. */
val PGPSignature.isHardRevocation val PGPSignature.isHardRevocation
get() = get() =
when (SignatureType.fromCode(signatureType)) { when (SignatureType.requireFromCode(signatureType)) {
null -> false
SignatureType.KEY_REVOCATION, SignatureType.KEY_REVOCATION,
SignatureType.SUBKEY_REVOCATION, SignatureType.SUBKEY_REVOCATION,
SignatureType.CERTIFICATION_REVOCATION -> { SignatureType.CERTIFICATION_REVOCATION -> {
@ -105,4 +104,4 @@ val PGPSignature.signatureHashAlgorithm: HashAlgorithm
get() = HashAlgorithm.requireFromId(hashAlgorithm) get() = HashAlgorithm.requireFromId(hashAlgorithm)
fun PGPSignature.isOfType(type: SignatureType): Boolean = fun PGPSignature.isOfType(type: SignatureType): Boolean =
SignatureType.fromCode(signatureType) == type SignatureType.requireFromCode(signatureType) == type

View file

@ -4,11 +4,7 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import org.bouncycastle.bcpg.AEADEncDataPacket
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket
import org.bouncycastle.openpgp.PGPPrivateKey import org.bouncycastle.openpgp.PGPPrivateKey
import org.bouncycastle.openpgp.PGPSessionKey
import org.bouncycastle.openpgp.operator.PGPDataDecryptor
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory
import org.bouncycastle.util.encoders.Base64 import org.bouncycastle.util.encoders.Base64
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
@ -25,34 +21,16 @@ import org.pgpainless.key.SubkeyIdentifier
class CachingBcPublicKeyDataDecryptorFactory( class CachingBcPublicKeyDataDecryptorFactory(
privateKey: PGPPrivateKey, privateKey: PGPPrivateKey,
override val subkeyIdentifier: SubkeyIdentifier override val subkeyIdentifier: SubkeyIdentifier
) : CustomPublicKeyDataDecryptorFactory() { ) : BcPublicKeyDataDecryptorFactory(privateKey), CustomPublicKeyDataDecryptorFactory {
private val decryptorFactory: BcPublicKeyDataDecryptorFactory =
BcPublicKeyDataDecryptorFactory(privateKey)
private val cachedSessions: MutableMap<String, ByteArray> = mutableMapOf() private val cachedSessions: MutableMap<String, ByteArray> = mutableMapOf()
override fun createDataDecryptor(p0: Boolean, p1: Int, p2: ByteArray?): PGPDataDecryptor {
return decryptorFactory.createDataDecryptor(p0, p1, p2)
}
override fun createDataDecryptor(p0: AEADEncDataPacket?, p1: PGPSessionKey?): PGPDataDecryptor {
return decryptorFactory.createDataDecryptor(p0, p1)
}
override fun createDataDecryptor(
p0: SymmetricEncIntegrityPacket?,
p1: PGPSessionKey?
): PGPDataDecryptor {
return decryptorFactory.createDataDecryptor(p0, p1)
}
override fun recoverSessionData( override fun recoverSessionData(
keyAlgorithm: Int, keyAlgorithm: Int,
secKeyData: Array<out ByteArray>, secKeyData: Array<out ByteArray>
pkeskVersion: Int
): ByteArray = ): ByteArray =
lookupSessionKeyData(secKeyData) lookupSessionKeyData(secKeyData)
?: costlyRecoverSessionData(keyAlgorithm, secKeyData, pkeskVersion).also { ?: costlyRecoverSessionData(keyAlgorithm, secKeyData).also {
cacheSessionKeyData(secKeyData, it) cacheSessionKeyData(secKeyData, it)
} }
@ -61,9 +39,8 @@ class CachingBcPublicKeyDataDecryptorFactory(
private fun costlyRecoverSessionData( private fun costlyRecoverSessionData(
keyAlgorithm: Int, keyAlgorithm: Int,
secKeyData: Array<out ByteArray>, secKeyData: Array<out ByteArray>
pkeskVersion: Int ): ByteArray = super.recoverSessionData(keyAlgorithm, secKeyData)
): ByteArray = decryptorFactory.recoverSessionData(keyAlgorithm, secKeyData, pkeskVersion)
private fun cacheSessionKeyData(secKeyData: Array<out ByteArray>, sessionKey: ByteArray) { private fun cacheSessionKeyData(secKeyData: Array<out ByteArray>, sessionKey: ByteArray) {
cachedSessions[toKey(secKeyData)] = sessionKey.clone() cachedSessions[toKey(secKeyData)] = sessionKey.clone()

View file

@ -4,7 +4,6 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import org.bouncycastle.openpgp.operator.AbstractPublicKeyDataDecryptorFactory
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
@ -15,7 +14,7 @@ import org.pgpainless.key.SubkeyIdentifier
* *
* @see [ConsumerOptions.addCustomDecryptorFactory] * @see [ConsumerOptions.addCustomDecryptorFactory]
*/ */
abstract class CustomPublicKeyDataDecryptorFactory : AbstractPublicKeyDataDecryptorFactory() { interface CustomPublicKeyDataDecryptorFactory : PublicKeyDataDecryptorFactory {
/** /**
* Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory] is * Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory] is
@ -23,5 +22,5 @@ abstract class CustomPublicKeyDataDecryptorFactory : AbstractPublicKeyDataDecryp
* *
* @return subkey identifier * @return subkey identifier
*/ */
abstract val subkeyIdentifier: SubkeyIdentifier val subkeyIdentifier: SubkeyIdentifier
} }

View file

@ -29,17 +29,11 @@ class HardwareSecurity {
* @param keyId id of the key * @param keyId id of the key
* @param keyAlgorithm algorithm * @param keyAlgorithm algorithm
* @param sessionKeyData encrypted session key * @param sessionKeyData encrypted session key
* @param pkeskVersion version of the Public-Key-Encrypted-Session-Key packet (3 or 6)
* @return decrypted session key * @return decrypted session key
* @throws HardwareSecurityException exception * @throws HardwareSecurityException exception
*/ */
@Throws(HardwareSecurityException::class) @Throws(HardwareSecurityException::class)
fun decryptSessionKey( fun decryptSessionKey(keyId: Long, keyAlgorithm: Int, sessionKeyData: ByteArray): ByteArray
keyId: Long,
keyAlgorithm: Int,
sessionKeyData: ByteArray,
pkeskVersion: Int
): ByteArray
} }
/** /**
@ -50,7 +44,7 @@ class HardwareSecurity {
class HardwareDataDecryptorFactory( class HardwareDataDecryptorFactory(
override val subkeyIdentifier: SubkeyIdentifier, override val subkeyIdentifier: SubkeyIdentifier,
private val callback: DecryptionCallback, private val callback: DecryptionCallback,
) : CustomPublicKeyDataDecryptorFactory() { ) : CustomPublicKeyDataDecryptorFactory {
// luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument. // luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument.
private val factory: PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(null) private val factory: PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(null)
@ -79,12 +73,10 @@ class HardwareSecurity {
override fun recoverSessionData( override fun recoverSessionData(
keyAlgorithm: Int, keyAlgorithm: Int,
secKeyData: Array<out ByteArray>, secKeyData: Array<out ByteArray>
pkeskVersion: Int
): ByteArray { ): ByteArray {
return try { return try {
callback.decryptSessionKey( callback.decryptSessionKey(subkeyIdentifier.subkeyId, keyAlgorithm, secKeyData[0])
subkeyIdentifier.subkeyId, keyAlgorithm, secKeyData[0], pkeskVersion)
} catch (e: HardwareSecurityException) { } catch (e: HardwareSecurityException) {
throw PGPException("Hardware-backed decryption failed.", e) throw PGPException("Hardware-backed decryption failed.", e)
} }

View file

@ -37,7 +37,7 @@ class CRLFGeneratorStream(private val crlfOut: OutputStream, encoding: StreamEnc
} }
override fun close() { override fun close() {
if (!isBinary && lastB == '\r'.code) { if (!isBinary && lastB == 'r'.code) {
crlfOut.write('\n'.code) crlfOut.write('\n'.code)
} }
crlfOut.close() crlfOut.close()

View file

@ -45,7 +45,7 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
} }
override fun addUserId(userId: CharSequence): KeyRingBuilder = apply { override fun addUserId(userId: CharSequence): KeyRingBuilder = apply {
userIds[userId.toString()] = null userIds[userId.toString().trim()] = null
} }
override fun addUserId(userId: ByteArray): KeyRingBuilder = override fun addUserId(userId: ByteArray): KeyRingBuilder =

View file

@ -67,9 +67,11 @@ abstract class KeyAccessor(protected val info: KeyRingInfo, protected val key: S
info.getLatestUserIdCertification(userId).let { if (it != null) return it } info.getLatestUserIdCertification(userId).let { if (it != null) return it }
} }
return info.getCurrentSubkeyBindingSignature(key.subkeyId) if (info.latestDirectKeySelfSignature != null) {
?: throw NoSuchElementException( return info.latestDirectKeySelfSignature
"Key does not carry acceptable self-signature signature.") }
return info.getCurrentSubkeyBindingSignature(key.subkeyId)!!
} }
} }

View file

@ -172,8 +172,11 @@ class KeyRingInfo(
primaryUserIdCertification?.let { getKeyExpirationTimeAsDate(it, publicKey) } primaryUserIdCertification?.let { getKeyExpirationTimeAsDate(it, publicKey) }
if (latestDirectKeySelfSignature == null && primaryUserIdCertification == null) { if (latestDirectKeySelfSignature == null && primaryUserIdCertification == null) {
/*
throw NoSuchElementException( throw NoSuchElementException(
"No direct-key signature and no user-id signature found.") "No direct-key signature and no user-id signature found.")
*/
return null
} }
if (directKeyExpirationDate != null && userIdExpirationDate == null) { if (directKeyExpirationDate != null && userIdExpirationDate == null) {
return directKeyExpirationDate return directKeyExpirationDate

View file

@ -478,7 +478,7 @@ class SecretKeyRingEditor(
val prevBinding = val prevBinding =
inspectKeyRing(secretKeyRing).getCurrentSubkeyBindingSignature(keyId) inspectKeyRing(secretKeyRing).getCurrentSubkeyBindingSignature(keyId)
?: throw NoSuchElementException( ?: throw NoSuchElementException(
"Previous subkey binding signature for ${keyId.openPgpKeyId()} MUST NOT be null.") "Previous subkey binding signaure for ${keyId.openPgpKeyId()} MUST NOT be null.")
val bindingSig = reissueSubkeyBindingSignature(subkey, expiration, protector, prevBinding) val bindingSig = reissueSubkeyBindingSignature(subkey, expiration, protector, prevBinding)
secretKeyRing = injectCertification(secretKeyRing, subkey, bindingSig) secretKeyRing = injectCertification(secretKeyRing, subkey, bindingSig)
} }
@ -569,10 +569,9 @@ class SecretKeyRingEditor(
} }
private fun sanitizeUserId(userId: CharSequence): CharSequence = private fun sanitizeUserId(userId: CharSequence): CharSequence =
// I'm not sure, what kind of sanitization is needed. // TODO: Further research how to sanitize user IDs.
// Newlines are allowed, they just need to be escaped when emitted in an ASCII armor header // e.g. what about newlines?
// Trailing/Leading whitespace is also fine. userId.toString().trim()
userId.toString()
private fun callbackFromRevocationAttributes(attributes: RevocationAttributes?) = private fun callbackFromRevocationAttributes(attributes: RevocationAttributes?) =
object : RevocationSignatureSubpackets.Callback { object : RevocationSignatureSubpackets.Callback {

View file

@ -52,7 +52,7 @@ class RevocationSignatureBuilder : AbstractSignatureBuilder<RevocationSignatureB
require(revokeeKey.isMasterKey) { require(revokeeKey.isMasterKey) {
"Signature type is KEY_REVOCATION, but provided revokee does not appear to be a primary key." "Signature type is KEY_REVOCATION, but provided revokee does not appear to be a primary key."
} }
it.generateCertification(revokeeKey) it.generateCertification(publicSigningKey)
} else { } else {
it.generateCertification(publicSigningKey, revokeeKey) it.generateCertification(publicSigningKey, revokeeKey)
} }

View file

@ -235,8 +235,7 @@ abstract class SignatureValidator {
signature: PGPSignature, signature: PGPSignature,
policy: Policy policy: Policy
): Policy.HashAlgorithmPolicy { ): Policy.HashAlgorithmPolicy {
return when (SignatureType.fromCode(signature.signatureType)) { return when (SignatureType.requireFromCode(signature.signatureType)) {
null -> policy.certificationSignatureHashAlgorithmPolicy
SignatureType.CERTIFICATION_REVOCATION, SignatureType.CERTIFICATION_REVOCATION,
SignatureType.KEY_REVOCATION, SignatureType.KEY_REVOCATION,
SignatureType.SUBKEY_REVOCATION -> policy.revocationSignatureHashAlgorithmPolicy SignatureType.SUBKEY_REVOCATION -> policy.revocationSignatureHashAlgorithmPolicy
@ -599,8 +598,7 @@ abstract class SignatureValidator {
if (signatureType.none { signature.isOfType(it) }) { if (signatureType.none { signature.isOfType(it) }) {
throw SignatureValidationException( throw SignatureValidationException(
"Signature is of type" + "Signature is of type" +
" ${SignatureType.fromCode(signature.signatureType) ?: " ${SignatureType.requireFromCode(signature.signatureType)}, " +
("0x" + signature.signatureType.toString(16))}, " +
"while only ${signatureType.contentToString()} are allowed here.") "while only ${signatureType.contentToString()} are allowed here.")
} }
} }
@ -690,7 +688,7 @@ abstract class SignatureValidator {
} }
if (notAfter != null && timestamp > notAfter) { if (notAfter != null && timestamp > notAfter) {
throw SignatureValidationException( throw SignatureValidationException(
"Signature was made after the latest allowed signature creation time." + "Signature was made before the latest allowed signature creation time." +
" Created: ${timestamp.formatUTC()}," + " Created: ${timestamp.formatUTC()}," +
" latest allowed: ${notAfter.formatUTC()}") " latest allowed: ${notAfter.formatUTC()}")
} }

View file

@ -59,13 +59,12 @@ class SignatureVerifier {
policy: Policy, policy: Policy,
referenceTime: Date referenceTime: Date
): Boolean { ): Boolean {
val type = SignatureType.fromCode(signature.signatureType) val type = SignatureType.requireFromCode(signature.signatureType)
return when (type) { return when (type) {
SignatureType.GENERIC_CERTIFICATION, SignatureType.GENERIC_CERTIFICATION,
SignatureType.NO_CERTIFICATION, SignatureType.NO_CERTIFICATION,
SignatureType.CASUAL_CERTIFICATION, SignatureType.CASUAL_CERTIFICATION,
SignatureType.POSITIVE_CERTIFICATION, SignatureType.POSITIVE_CERTIFICATION ->
null ->
verifyUserIdCertification( verifyUserIdCertification(
userId, signature, signingKey, keyWithUserId, policy, referenceTime) userId, signature, signingKey, keyWithUserId, policy, referenceTime)
SignatureType.CERTIFICATION_REVOCATION -> SignatureType.CERTIFICATION_REVOCATION ->

View file

@ -247,9 +247,7 @@ class ArmorUtils {
.add(OpenPgpFingerprint.of(publicKey).prettyPrint()) .add(OpenPgpFingerprint.of(publicKey).prettyPrint())
// Primary / First User ID // Primary / First User ID
(primary ?: first)?.let { (primary ?: first)?.let {
headerMap headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add(it)
.getOrPut(HEADER_COMMENT) { mutableSetOf() }
.add(it.replace("\n", "\\n").replace("\r", "\\r"))
} }
// X-1 further identities // X-1 further identities
when (userIds.size) { when (userIds.size) {

View file

@ -11,9 +11,14 @@ import org.bouncycastle.util.Arrays
* *
* @param chars may be null for empty passwords. * @param chars may be null for empty passwords.
*/ */
class Passphrase(private val chars: CharArray?) { class Passphrase(chars: CharArray?) {
private val lock = Any() private val lock = Any()
private var valid = true private var valid = true
private val chars: CharArray?
init {
this.chars = trimWhitespace(chars)
}
/** /**
* Return a copy of the underlying char array. A return value of null represents an empty * Return a copy of the underlying char array. A return value of null represents an empty
@ -62,13 +67,6 @@ class Passphrase(private val chars: CharArray?) {
override fun hashCode(): Int = getChars()?.let { String(it) }.hashCode() override fun hashCode(): Int = getChars()?.let { String(it) }.hashCode()
/**
* Return a copy of this [Passphrase], but with whitespace characters trimmed off.
*
* @return copy with trimmed whitespace
*/
fun withTrimmedWhitespace(): Passphrase = Passphrase(trimWhitespace(chars))
companion object { companion object {
/** /**

View file

@ -5,6 +5,7 @@
package org.pgpainless.algorithm; package org.pgpainless.algorithm;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -30,6 +31,6 @@ public class SignatureTypeTest {
assertFalse(SignatureType.isRevocationSignature(SignatureType.STANDALONE.getCode())); assertFalse(SignatureType.isRevocationSignature(SignatureType.STANDALONE.getCode()));
assertFalse(SignatureType.isRevocationSignature(SignatureType.TIMESTAMP.getCode())); assertFalse(SignatureType.isRevocationSignature(SignatureType.TIMESTAMP.getCode()));
assertFalse(SignatureType.isRevocationSignature(-3)); assertThrows(IllegalArgumentException.class, () -> SignatureType.isRevocationSignature(-3));
} }
} }

View file

@ -13,7 +13,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.CompressionAlgorithmTags;
@ -31,9 +30,6 @@ import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.util.io.Streams; import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.DocumentSignatureType; import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.HashAlgorithm;
@ -292,9 +288,11 @@ public class CanonicalizedDataEncryptionTest {
} }
} }
@ParameterizedTest @Test
@MethodSource("resultOfDecryptionIsCRLFEncodedArguments") public void resultOfDecryptionIsCRLFEncoded() throws PGPException, IOException {
public void resultOfDecryptionIsCRLFEncoded(String before, String after) throws PGPException, IOException { String before = "Foo\nBar!\n";
String after = "Foo\r\nBar!\r\n";
String encrypted = encryptAndSign(before, DocumentSignatureType.BINARY_DOCUMENT, StreamEncoding.TEXT, true); String encrypted = encryptAndSign(before, DocumentSignatureType.BINARY_DOCUMENT, StreamEncoding.TEXT, true);
ByteArrayInputStream in = new ByteArrayInputStream(encrypted.getBytes(StandardCharsets.UTF_8)); ByteArrayInputStream in = new ByteArrayInputStream(encrypted.getBytes(StandardCharsets.UTF_8));
@ -311,16 +309,6 @@ public class CanonicalizedDataEncryptionTest {
assertArrayEquals(after.getBytes(StandardCharsets.UTF_8), decrypted.toByteArray()); assertArrayEquals(after.getBytes(StandardCharsets.UTF_8), decrypted.toByteArray());
} }
private static Stream<Arguments> resultOfDecryptionIsCRLFEncodedArguments() {
return Stream.of(
Arguments.of("foo", "foo"),
Arguments.of("rrr", "rrr"),
Arguments.of("Foo\nBar!\n", "Foo\r\nBar!\r\n"),
Arguments.of("Foo\rBar!\r", "Foo\r\nBar!\r\n"),
Arguments.of("Foo\r\nBar!\r\n", "Foo\r\nBar!\r\n")
);
}
@Test @Test
public void resultOfDecryptionIsNotCRLFEncoded() throws PGPException, IOException { public void resultOfDecryptionIsNotCRLFEncoded() throws PGPException, IOException {
String beforeAndAfter = "Foo\nBar!\n"; String beforeAndAfter = "Foo\nBar!\n";

View file

@ -55,14 +55,14 @@ public class CustomPublicKeyDataDecryptorFactoryTest {
HardwareSecurity.DecryptionCallback hardwareDecryptionCallback = new HardwareSecurity.DecryptionCallback() { HardwareSecurity.DecryptionCallback hardwareDecryptionCallback = new HardwareSecurity.DecryptionCallback() {
@Override @Override
public byte[] decryptSessionKey(long keyId, int keyAlgorithm, byte[] sessionKeyData, int pkeskVersion) public byte[] decryptSessionKey(long keyId, int keyAlgorithm, byte[] sessionKeyData)
throws HardwareSecurity.HardwareSecurityException { throws HardwareSecurity.HardwareSecurityException {
// Emulate hardware decryption. // Emulate hardware decryption.
try { try {
PGPSecretKey decryptionKey = secretKey.getSecretKey(encryptionKey.getKeyID()); PGPSecretKey decryptionKey = secretKey.getSecretKey(encryptionKey.getKeyID());
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(decryptionKey, Passphrase.emptyPassphrase()); PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(decryptionKey, Passphrase.emptyPassphrase());
PublicKeyDataDecryptorFactory internal = new BcPublicKeyDataDecryptorFactory(privateKey); PublicKeyDataDecryptorFactory internal = new BcPublicKeyDataDecryptorFactory(privateKey);
return internal.recoverSessionData(keyAlgorithm, new byte[][] {sessionKeyData}, pkeskVersion); return internal.recoverSessionData(keyAlgorithm, new byte[][] {sessionKeyData});
} catch (PGPException e) { } catch (PGPException e) {
throw new HardwareSecurity.HardwareSecurityException(); throw new HardwareSecurity.HardwareSecurityException();
} }

View file

@ -24,7 +24,7 @@ public class MessageMetadataTest {
@Test @Test
public void processTestMessage_COMP_ENC_ENC_LIT() { public void processTestMessage_COMP_ENC_ENC_LIT() {
// Note: COMP of ENC does not make sense, since ENC is indistinguishable from randomness // Note: COMP of ENC does not make sense, since ENC is indistinguishable from randomness
// and randomness cannot be compressed. // and randomness cannot be encrypted.
// For the sake of testing though, this is okay. // For the sake of testing though, this is okay.
MessageMetadata.Message message = new MessageMetadata.Message(); MessageMetadata.Message message = new MessageMetadata.Message();

View file

@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key
import java.io.ByteArrayOutputStream
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.bouncycastle.util.io.Streams
import org.junit.jupiter.api.Test
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.decryption_verification.ConsumerOptions
import org.pgpainless.encryption_signing.EncryptionOptions
import org.pgpainless.encryption_signing.ProducerOptions
import org.pgpainless.encryption_signing.SigningOptions
import org.pgpainless.key.generation.KeySpec
import org.pgpainless.key.generation.type.KeyType
import org.pgpainless.key.generation.type.eddsa_legacy.EdDSALegacyCurve
import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec
import org.pgpainless.key.protection.SecretKeyRingProtector
class KeyWithoutSelfSigsTest {
@Test
fun signAndVerify() {
val key = PGPainless.readKeyRing().secretKeyRing(KEY)
val cert = PGPainless.extractCertificate(key!!)
val ciphertextOut = ByteArrayOutputStream()
val encryptionStream =
PGPainless.encryptAndOrSign()
.onOutputStream(ciphertextOut)
.withOptions(
ProducerOptions.signAndEncrypt(
EncryptionOptions.encryptCommunications().addRecipient(cert),
SigningOptions.get()
.addSignature(SecretKeyRingProtector.unprotectedKeys(), key)))
encryptionStream.write("Hello, World!\n".toByteArray())
encryptionStream.close()
val plaintextOut = ByteArrayOutputStream()
val decryptionStream =
PGPainless.decryptAndOrVerify()
.onInputStream(ciphertextOut.toByteArray().inputStream())
.withOptions(
ConsumerOptions.get()
.addVerificationCert(cert)
.addDecryptionKey(key, SecretKeyRingProtector.unprotectedKeys()))
Streams.pipeAll(decryptionStream, plaintextOut)
decryptionStream.close()
}
fun generateKey() {
val key =
PGPainless.buildKeyRing()
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519)))
.addSubkey(
KeySpec.getBuilder(
KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.SIGN_DATA))
.addSubkey(
KeySpec.getBuilder(
KeyType.XDH_LEGACY(XDHLegacySpec._X25519),
KeyFlag.ENCRYPT_STORAGE,
KeyFlag.ENCRYPT_COMMS))
.build()
.let {
var cert = PGPainless.extractCertificate(it)
cert =
PGPPublicKeyRing(
buildList {
val iterator = cert.publicKeys
val primaryKey = iterator.next()
add(
PGPPublicKey.removeCertification(
primaryKey, primaryKey.signatures.next()))
while (iterator.hasNext()) {
add(iterator.next())
}
})
PGPSecretKeyRing.replacePublicKeys(it, cert)
}
println(PGPainless.asciiArmor(key))
}
companion object {
const val KEY =
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: DA3E CC77 1CD6 46F0 C6C4 4FDA 86A3 7B22 7802 2FC7\n" +
"\n" +
"lFgEZUuWuhYJKwYBBAHaRw8BAQdAuXfarON/+UG1qwhVy4/VCYuEb9iLFLb8KGQt\n" +
"KfX4Se0AAQDgqGHsb2M43F+6wK5Hla+oZzFkTUsBx8HMpRx2yeQT6hFAnFgEZUuW\n" +
"uhYJKwYBBAHaRw8BAQdAx0OHISLtekltdUVGGrG/Gs3asc/jG/nqCkBEZ5uyELwA\n" +
"AP0faf8bprP3fj248/NacfynKEVnjzc1gocfhGiWrnVgAxC1iNUEGBYKAH0FAmVL\n" +
"lroCngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJlS5a6AAoJED9gFx9r\n" +
"B25syqoA/0JR3Zcs6fHQ0jW7+u6330SD5h8WvG78IKsE6AfChBLXAP4hlXGidztq\n" +
"5sOHEQvXD2KPCHEJ6MuQ+rbNSSf0fQhgDwAKCRCGo3sieAIvxzmIAP9+9vRoevUM\n" +
"luQhZzQ7DgYqTCyNkeq2cpVgOfa0lyVDgwEApwrd5DlU3GorGHAQHFS6jhw1IOoG\n" +
"FGQ3zpWaOXd7XwKcXQRlS5a6EgorBgEEAZdVAQUBAQdAZIY7ISyNzp0oMoK0dgb8\n" +
"dX6t/i4Uh+l0jnxM0Z1dEB8DAQgHAAD/fhL5dzdJQ7hFhr78AmDEZKFE4txZFPvd\n" +
"ZVFvIWTthFgQ5Ih1BBgWCgAdBQJlS5a6Ap4BApsMBRYCAwEABAsJCAcFFQoJCAsA\n" +
"CgkQhqN7IngCL8cIGgEAzydjTfKvdrTvzXXu97j8TAoOxk89QnLqsM6BU0VsVmkA\n" +
"/1IzH+PXgPPW9ff+elxTi2NWmK+P033P6i5b5Jdf41YD\n" +
"=GBVS\n" +
"-----END PGP PRIVATE KEY BLOCK-----"
}
}

View file

@ -23,7 +23,7 @@ To start using pgpainless-sop in your code, include the following lines in your
... ...
dependencies { dependencies {
... ...
implementation "org.pgpainless:pgpainless-sop:1.7.6" implementation "org.pgpainless:pgpainless-sop:1.7.2"
... ...
} }
@ -34,7 +34,7 @@ dependencies {
<dependency> <dependency>
<groupId>org.pgpainless</groupId> <groupId>org.pgpainless</groupId>
<artifactId>pgpainless-sop</artifactId> <artifactId>pgpainless-sop</artifactId>
<version>1.7.6</version> <version>1.7.2</version>
</dependency> </dependency>
... ...
</dependencies> </dependencies>
@ -67,7 +67,7 @@ byte[] encrypted = sop.encrypt()
// Decrypt a message // Decrypt a message
ByteArrayAndResult<DecryptionResult> messageAndVerifications = sop.decrypt() ByteArrayAndResult<DecryptionResult> messageAndVerifications = sop.decrypt()
.verifyWithCert(cert) .verifyWith(cert)
.withKey(key) .withKey(key)
.ciphertext(encrypted) .ciphertext(encrypted)
.toByteArrayAndResult(); .toByteArrayAndResult();

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org> // SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
// //
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
import org.apache.tools.ant.filters.*
plugins { plugins {
id 'java-library' id 'java-library'
} }
@ -22,7 +22,7 @@ dependencies {
testImplementation "ch.qos.logback:logback-classic:$logbackVersion" testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
// Depend on "shared" sop-java test suite (fixtures are turned into tests by inheritance inside test sources) // Depend on "shared" sop-java test suite (fixtures are turned into tests by inheritance inside test sources)
testImplementation "org.pgpainless:sop-java-testfixtures:$sopJavaVersion" testImplementation(testFixtures("org.pgpainless:sop-java:$sopJavaVersion"))
implementation(project(":pgpainless-core")) implementation(project(":pgpainless-core"))
api "org.pgpainless:sop-java:$sopJavaVersion" api "org.pgpainless:sop-java:$sopJavaVersion"
@ -30,12 +30,6 @@ dependencies {
implementation "com.google.code.findbugs:jsr305:3.0.2" implementation "com.google.code.findbugs:jsr305:3.0.2"
} }
processResources {
filter ReplaceTokens, tokens: [
"project.version": project.version.toString()
]
}
test { test {
useJUnitPlatform() useJUnitPlatform()
environment("test.implementation", "sop.testsuite.pgpainless.PGPainlessSopInstanceFactory") environment("test.implementation", "sop.testsuite.pgpainless.PGPainlessSopInstanceFactory")

View file

@ -12,6 +12,7 @@ import org.bouncycastle.util.io.Streams
import org.pgpainless.decryption_verification.OpenPgpInputStream import org.pgpainless.decryption_verification.OpenPgpInputStream
import org.pgpainless.util.ArmoredOutputStreamFactory import org.pgpainless.util.ArmoredOutputStreamFactory
import sop.Ready import sop.Ready
import sop.enums.ArmorLabel
import sop.exception.SOPGPException import sop.exception.SOPGPException
import sop.operation.Armor import sop.operation.Armor
@ -45,4 +46,9 @@ class ArmorImpl : Armor {
} }
} }
} }
@Deprecated("Setting custom labels is not supported.")
override fun label(label: ArmorLabel): Armor {
throw SOPGPException.UnsupportedOption("Setting custom Armor labels not supported.")
}
} }

View file

@ -11,7 +11,6 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.PGPSignature
import org.bouncycastle.util.io.Streams import org.bouncycastle.util.io.Streams
import org.pgpainless.PGPainless import org.pgpainless.PGPainless
import org.pgpainless.algorithm.CompressionAlgorithm
import org.pgpainless.algorithm.DocumentSignatureType import org.pgpainless.algorithm.DocumentSignatureType
import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.HashAlgorithm
import org.pgpainless.bouncycastle.extensions.openPgpFingerprint import org.pgpainless.bouncycastle.extensions.openPgpFingerprint
@ -58,10 +57,7 @@ class DetachedSignImpl : DetachedSign {
val signingStream = val signingStream =
PGPainless.encryptAndOrSign() PGPainless.encryptAndOrSign()
.discardOutput() .discardOutput()
.withOptions( .withOptions(ProducerOptions.sign(signingOptions).setAsciiArmor(armor))
ProducerOptions.sign(signingOptions)
.setAsciiArmor(armor)
.overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED))
return object : ReadyWithResult<SigningResult>() { return object : ReadyWithResult<SigningResult>() {
override fun writeTo(outputStream: OutputStream): SigningResult { override fun writeTo(outputStream: OutputStream): SigningResult {

View file

@ -11,9 +11,7 @@ import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPSecretKeyRing import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.bouncycastle.util.io.Streams import org.bouncycastle.util.io.Streams
import org.pgpainless.PGPainless import org.pgpainless.PGPainless
import org.pgpainless.algorithm.CompressionAlgorithm
import org.pgpainless.algorithm.DocumentSignatureType import org.pgpainless.algorithm.DocumentSignatureType
import org.pgpainless.algorithm.StreamEncoding
import org.pgpainless.bouncycastle.extensions.openPgpFingerprint import org.pgpainless.bouncycastle.extensions.openPgpFingerprint
import org.pgpainless.encryption_signing.ProducerOptions import org.pgpainless.encryption_signing.ProducerOptions
import org.pgpainless.encryption_signing.SigningOptions import org.pgpainless.encryption_signing.SigningOptions
@ -58,22 +56,12 @@ class InlineSignImpl : InlineSign {
val producerOptions = val producerOptions =
ProducerOptions.sign(signingOptions).apply { ProducerOptions.sign(signingOptions).apply {
when (mode) { if (mode == InlineSignAs.clearsigned) {
InlineSignAs.clearsigned -> { setCleartextSigned()
setCleartextSigned() setAsciiArmor(true) // CSF is always armored
setAsciiArmor(true) // CSF is always armored } else {
setEncoding(StreamEncoding.TEXT) setAsciiArmor(armor)
applyCRLFEncoding()
}
InlineSignAs.text -> {
setEncoding(StreamEncoding.TEXT)
applyCRLFEncoding()
}
else -> {
setAsciiArmor(armor)
}
} }
overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)
} }
return object : Ready() { return object : Ready() {

View file

@ -8,7 +8,6 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.* import java.util.*
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import sop.SOP
import sop.operation.Version import sop.operation.Version
/** Implementation of the `version` operation using PGPainless. */ /** Implementation of the `version` operation using PGPainless. */
@ -26,14 +25,14 @@ class VersionImpl : Version {
String.format(Locale.US, "Bouncy Castle %.2f", BouncyCastleProvider().version) String.format(Locale.US, "Bouncy Castle %.2f", BouncyCastleProvider().version)
val specVersion = String.format("%02d", SOP_VERSION) val specVersion = String.format("%02d", SOP_VERSION)
return """${getName()} ${getVersion()} return """${getName()} ${getVersion()}
https://codeberg.org/PGPainless/pgpainless/src/branch/main/pgpainless-sop https://codeberg.org/PGPainless/pgpainless/src/branch/master/pgpainless-sop
Implementation of the Stateless OpenPGP Protocol Version $specVersion Implementation of the Stateless OpenPGP Protocol Version $specVersion
https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-$specVersion https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-$specVersion
Based on pgpainless-core ${getVersion()} Based on pgpainless-core ${getVersion()}
https://pgpainless.org https://pgpainless.org
${formatSopJavaVersion()}
Using $bcVersion Using $bcVersion
https://www.bouncycastle.org/java.html""" https://www.bouncycastle.org/java.html"""
} }
@ -50,27 +49,15 @@ https://www.bouncycastle.org/java.html"""
// See https://stackoverflow.com/a/50119235 // See https://stackoverflow.com/a/50119235
return try { return try {
val resourceIn: InputStream = val resourceIn: InputStream =
SOP::class.java.getResourceAsStream("/pgpainless-sop.properties") javaClass.getResourceAsStream("/version.properties")
?: throw IOException("File pgpainless-sop.properties not found.") ?: throw IOException("File version.properties not found.")
val properties = Properties().apply { load(resourceIn) } val properties = Properties().apply { load(resourceIn) }
properties.getProperty("pgpainless-sop-version") properties.getProperty("version")
} catch (e: IOException) { } catch (e: IOException) {
"DEVELOPMENT" "DEVELOPMENT"
} }
} }
private fun formatSopJavaVersion(): String {
return getSopJavaVersion()?.let {
"""
sop-java $it
"""
.trimIndent()
}
?: ""
}
override fun isSopSpecImplementationIncomplete(): Boolean = false override fun isSopSpecImplementationIncomplete(): Boolean = false
} }

View file

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

View file

@ -100,14 +100,4 @@ public class GenerateKeyTest {
assertThrows(SOPGPException.UnsupportedProfile.class, () -> assertThrows(SOPGPException.UnsupportedProfile.class, () ->
sop.generateKey().profile("invalid")); sop.generateKey().profile("invalid"));
} }
@Test
public void generateKeyWithNewlinesInUserId() throws IOException {
byte[] keyBytes = sop.generateKey()
.userId("Foo\n\nBar")
.generate()
.getBytes();
assertTrue(new String(keyBytes).contains("Foo\\n\\nBar"));
}
} }

View file

@ -4,15 +4,16 @@
allprojects { allprojects {
ext { ext {
shortVersion = '1.7.7' shortVersion = '1.7.2'
isSnapshot = true isSnapshot = false
javaSourceCompatibility = 11 pgpainlessMinAndroidSdk = 10
bouncyCastleVersion = '1.81' javaSourceCompatibility = 1.8
bouncyCastleVersion = '1.78.1'
bouncyPgVersion = bouncyCastleVersion bouncyPgVersion = bouncyCastleVersion
junitVersion = '5.8.2' junitVersion = '5.8.2'
logbackVersion = '1.5.13' logbackVersion = '1.2.13' // 1.4+ cause CLI spam :/
mockitoVersion = '4.5.1' mockitoVersion = '4.5.1'
slf4jVersion = '1.7.36' slf4jVersion = '1.7.36'
sopJavaVersion = '10.1.1' sopJavaVersion = '10.0.3'
} }
} }