From 3b92ccc59d8c6f8c9ef4ccc5f5e8317659fee729 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 16:05:05 +0200 Subject: [PATCH 01/12] PGPainless 1.7.7-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 7ed1f065..003de912 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '1.7.6' - isSnapshot = false + shortVersion = '1.7.7' + isSnapshot = true javaSourceCompatibility = 11 bouncyCastleVersion = '1.80' bouncyPgVersion = bouncyCastleVersion From 5a413f53a42a26169901ce634b6dfa7cc002f22b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 21 Apr 2025 19:11:52 +0200 Subject: [PATCH 02/12] Specify license information for native-image metadata --- REUSE.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/REUSE.toml b/REUSE.toml index d250e0be..66b5e867 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -93,6 +93,12 @@ precedence = "aggregate" SPDX-FileCopyrightText = "2022 Paul Schaub , 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 " +SPDX-License-Identifier = "Apache-2.0" + [[annotations]] path = "pgpainless-cli/rewriteManPages.sh" precedence = "aggregate" From 0649c041cdead499b391f874830a0bdc2c17e246 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 21 Apr 2025 19:12:10 +0200 Subject: [PATCH 03/12] gradle: migrate to new shadow plugin namespace --- pgpainless-cli/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-cli/build.gradle b/pgpainless-cli/build.gradle index e86ddedf..e4c3f060 100644 --- a/pgpainless-cli/build.gradle +++ b/pgpainless-cli/build.gradle @@ -5,7 +5,7 @@ plugins { id 'application' id 'org.graalvm.buildtools.native' version '0.10.6' - id 'com.github.johnrengelman.shadow' version '8.1.1' + id 'com.gradleup.shadow' version '8.3.6' } graalvmNative { From 7953ade13673568487eb0a3b7b141f96a08af585 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Jun 2025 12:37:04 +0200 Subject: [PATCH 04/12] Bump checkstyle to 10.25.0 Fixes https://github.com/pgpainless/pgpainless/security/dependabot/24 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d05845b4..93100f4d 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ allprojects { // checkstyle checkstyle { - toolVersion = '10.12.1' + toolVersion = '10.25.0' } spotless { From 5f30df6d166962f780fbfcb870f9934d84e6e771 Mon Sep 17 00:00:00 2001 From: Felix Hagemans Date: Wed, 4 Jun 2025 16:02:23 +0200 Subject: [PATCH 05/12] Fixed typo in sop readme --- pgpainless-sop/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-sop/README.md b/pgpainless-sop/README.md index da112604..7f7267cd 100644 --- a/pgpainless-sop/README.md +++ b/pgpainless-sop/README.md @@ -67,7 +67,7 @@ byte[] encrypted = sop.encrypt() // Decrypt a message ByteArrayAndResult messageAndVerifications = sop.decrypt() - .verifyWith(cert) + .verifyWithCert(cert) .withKey(key) .ciphertext(encrypted) .toByteArrayAndResult(); From 0f54cc615c5409b5fa12d61182f63d022e78f828 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 10:30:42 +0200 Subject: [PATCH 06/12] Bump BC to 1.81, update native-image reflect-config, resource-config --- .../META-INF/native-image/reflect-config.json | 108 +----------------- .../native-image/resource-config.json | 2 - version.gradle | 2 +- 3 files changed, 5 insertions(+), 107 deletions(-) diff --git a/pgpainless-cli/src/main/resources/META-INF/native-image/reflect-config.json b/pgpainless-cli/src/main/resources/META-INF/native-image/reflect-config.json index 25ad3faf..63bdf5f3 100644 --- a/pgpainless-cli/src/main/resources/META-INF/native-image/reflect-config.json +++ b/pgpainless-cli/src/main/resources/META-INF/native-image/reflect-config.json @@ -1,16 +1,7 @@ [ -{ - "name":"[B" -}, { "name":"[Ljava.lang.Object;" }, -{ - "name":"[Ljava.lang.String;" -}, -{ - "name":"[Lsun.security.pkcs.SignerInfo;" -}, { "name":"ch.qos.logback.classic.encoder.PatternLayoutEncoder", "queryAllPublicMethods":true, @@ -70,9 +61,6 @@ { "name":"java.lang.RuntimePermission" }, -{ - "name":"java.lang.String" -}, { "name":"java.lang.System", "methods":[{"name":"console","parameterTypes":[] }] @@ -101,9 +89,6 @@ "name":"java.nio.file.Paths", "methods":[{"name":"get","parameterTypes":["java.lang.String","java.lang.String[]"] }] }, -{ - "name":"java.security.AlgorithmParametersSpi" -}, { "name":"java.security.AllPermission" }, @@ -119,21 +104,6 @@ { "name":"java.security.cert.PKIXRevocationChecker" }, -{ - "name":"java.security.interfaces.DSAPrivateKey" -}, -{ - "name":"java.security.interfaces.DSAPublicKey" -}, -{ - "name":"java.security.interfaces.RSAPrivateKey" -}, -{ - "name":"java.security.interfaces.RSAPublicKey" -}, -{ - "name":"java.security.spec.DSAParameterSpec" -}, { "name":"java.sql.Connection" }, @@ -208,9 +178,6 @@ "name":"java.time.ZonedDateTime", "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] }, -{ - "name":"java.util.Date" -}, { "name":"java.util.HashSet" }, @@ -245,11 +212,6 @@ { "name":"java.util.concurrent.locks.ReentrantLock$Sync" }, -{ - "name":"javax.security.auth.x500.X500Principal", - "fields":[{"name":"thisX500Name"}], - "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] -}, { "name":"javax.smartcardio.CardPermission" }, @@ -333,6 +295,10 @@ "name":"org.bouncycastle.jcajce.provider.asymmetric.NTRU$Mappings", "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.NoSig$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings", "methods":[{"name":"","parameterTypes":[] }] @@ -914,18 +880,6 @@ "queryAllDeclaredMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, -{ - "name":"sun.security.provider.DSA$SHA256withDSA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.DSAKeyFactory", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.DSAParameters", - "methods":[{"name":"","parameterTypes":[] }] -}, { "name":"sun.security.provider.NativePRNG", "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] @@ -933,59 +887,5 @@ { "name":"sun.security.provider.SHA", "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA2$SHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.X509Factory", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.rsa.RSAKeyFactory$Legacy", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.rsa.RSASignature$SHA256withRSA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.util.ObjectIdentifier" -}, -{ - "name":"sun.security.x509.AuthorityInfoAccessExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.AuthorityKeyIdentifierExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.BasicConstraintsExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.CRLDistributionPointsExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.CertificateExtensions" -}, -{ - "name":"sun.security.x509.CertificatePoliciesExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.ExtendedKeyUsageExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.KeyUsageExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.SubjectKeyIdentifierExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] } ] \ No newline at end of file diff --git a/pgpainless-cli/src/main/resources/META-INF/native-image/resource-config.json b/pgpainless-cli/src/main/resources/META-INF/native-image/resource-config.json index af650dc9..3c66a520 100644 --- a/pgpainless-cli/src/main/resources/META-INF/native-image/resource-config.json +++ b/pgpainless-cli/src/main/resources/META-INF/native-image/resource-config.json @@ -40,8 +40,6 @@ "pattern":"\\Qsop-java-version.properties\\E" }, { "pattern":"java.base:\\Qsun/text/resources/LineBreakIteratorData\\E" - }, { - "pattern":"java.base:\\Qsun/text/resources/nfkc.icu\\E" }]}, "bundles":[{ "name":"msg_armor", diff --git a/version.gradle b/version.gradle index 003de912..bc2515a4 100644 --- a/version.gradle +++ b/version.gradle @@ -7,7 +7,7 @@ allprojects { shortVersion = '1.7.7' isSnapshot = true javaSourceCompatibility = 11 - bouncyCastleVersion = '1.80' + bouncyCastleVersion = '1.81' bouncyPgVersion = bouncyCastleVersion junitVersion = '5.8.2' logbackVersion = '1.5.13' From 4cf6c6b16a2f8d352fe54d3b0276eff936caeb03 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 10:42:50 +0200 Subject: [PATCH 07/12] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f63d6e..13c93c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ SPDX-License-Identifier: CC0-1.0 # 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 From f2cbde43bee0da523717b65f10f95defc4ad6f60 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 1 Jul 2025 10:54:06 +0200 Subject: [PATCH 08/12] Update codeql action to v3 --- .github/workflows/codeql-analysis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 031ba56d..d45b16a3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'java' ] + language: [ 'java-kotlin' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support @@ -46,7 +46,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # 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). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -71,4 +71,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 0ee31b232ac7b4beda0940bcbd52fc078209dfc4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 23 Jul 2025 11:23:34 +0200 Subject: [PATCH 09/12] Allow UserIDs with trailing/leading whitespace and escape newlines in ASCII armor --- .../org/pgpainless/key/generation/KeyRingBuilder.kt | 2 +- .../modification/secretkeyring/SecretKeyRingEditor.kt | 7 ++++--- .../src/main/kotlin/org/pgpainless/util/ArmorUtils.kt | 3 ++- .../test/java/org/pgpainless/sop/GenerateKeyTest.java | 10 ++++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt index 05adf7d9..aacfcceb 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt @@ -45,7 +45,7 @@ class KeyRingBuilder : KeyRingBuilderInterface { } override fun addUserId(userId: CharSequence): KeyRingBuilder = apply { - userIds[userId.toString().trim()] = null + userIds[userId.toString()] = null } override fun addUserId(userId: ByteArray): KeyRingBuilder = diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt index ec93c6d6..56fb7695 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt @@ -569,9 +569,10 @@ class SecretKeyRingEditor( } private fun sanitizeUserId(userId: CharSequence): CharSequence = - // TODO: Further research how to sanitize user IDs. - // e.g. what about newlines? - userId.toString().trim() + // I'm not sure, what kind of sanitization is needed. + // Newlines are allowed, they just need to be escaped when emitted in an ASCII armor header + // Trailing/Leading whitespace is also fine. + userId.toString() private fun callbackFromRevocationAttributes(attributes: RevocationAttributes?) = object : RevocationSignatureSubpackets.Callback { diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt index b5d5b839..b6e802b2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt @@ -247,7 +247,8 @@ class ArmorUtils { .add(OpenPgpFingerprint.of(publicKey).prettyPrint()) // Primary / First User ID (primary ?: first)?.let { - headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add(it) + headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() } + .add(it.replace("\n", "\\n").replace("\r", "\\r")) } // X-1 further identities when (userIds.size) { diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateKeyTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateKeyTest.java index ca6df790..521cdfe0 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateKeyTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateKeyTest.java @@ -100,4 +100,14 @@ public class GenerateKeyTest { assertThrows(SOPGPException.UnsupportedProfile.class, () -> 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")); + } } From 9b0a3cd4c7ffe8866ab9622fde866b79cd34a62b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 23 Jul 2025 11:24:10 +0200 Subject: [PATCH 10/12] Do not trim passphrases automatically --- .../main/kotlin/org/pgpainless/util/Passphrase.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt index 4d1e49d2..bd25f2b9 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt @@ -11,14 +11,9 @@ import org.bouncycastle.util.Arrays * * @param chars may be null for empty passwords. */ -class Passphrase(chars: CharArray?) { +class Passphrase(private val chars: CharArray?) { private val lock = Any() 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 @@ -67,6 +62,13 @@ class Passphrase(chars: CharArray?) { 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 { /** From 0d807cb6b8317f2e878509b5cb5f4a9a91e5287d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 23 Jul 2025 11:25:31 +0200 Subject: [PATCH 11/12] Fix typo in error message --- .../key/modification/secretkeyring/SecretKeyRingEditor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt index 56fb7695..5480442d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt @@ -478,7 +478,7 @@ class SecretKeyRingEditor( val prevBinding = inspectKeyRing(secretKeyRing).getCurrentSubkeyBindingSignature(keyId) ?: throw NoSuchElementException( - "Previous subkey binding signaure for ${keyId.openPgpKeyId()} MUST NOT be null.") + "Previous subkey binding signature for ${keyId.openPgpKeyId()} MUST NOT be null.") val bindingSig = reissueSubkeyBindingSignature(subkey, expiration, protector, prevBinding) secretKeyRing = injectCertification(secretKeyRing, subkey, bindingSig) } From 0fe3a7abf660ee7814e87255c13b5775030e73f9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 30 Aug 2025 10:36:40 +0200 Subject: [PATCH 12/12] Fix ArmorUtils spotless complaint --- .../src/main/kotlin/org/pgpainless/util/ArmorUtils.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt index b6e802b2..db1cb54d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt @@ -247,7 +247,8 @@ class ArmorUtils { .add(OpenPgpFingerprint.of(publicKey).prettyPrint()) // Primary / First User ID (primary ?: first)?.let { - headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() } + headerMap + .getOrPut(HEADER_COMMENT) { mutableSetOf() } .add(it.replace("\n", "\\n").replace("\r", "\\r")) } // X-1 further identities