From 2ae2389666ad4b7925e20ccf463caed89fe7064e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Mar 2025 11:16:37 +0100 Subject: [PATCH] More code cleanup --- .../main/kotlin/org/pgpainless/PGPainless.kt | 2 +- .../encryption_signing/EncryptionBuilder.kt | 52 +++---------------- .../encryption_signing/EncryptionOptions.kt | 27 +++++++--- .../encryption_signing/EncryptionStream.kt | 22 +++----- .../encryption_signing/ProducerOptions.kt | 19 +++++-- .../weird_keys/TestTwoSubkeysEncryption.java | 3 +- 6 files changed, 51 insertions(+), 74 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt index 7a1599fb..d5839054 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt @@ -190,7 +190,7 @@ class PGPainless( * * @return builder */ - @JvmStatic fun encryptAndOrSign() = EncryptionBuilder() + @JvmStatic fun encryptAndOrSign() = EncryptionBuilder(getInstance()) /** * Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt index 43263a28..3f86aab5 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt @@ -5,65 +5,25 @@ package org.pgpainless.encryption_signing import java.io.OutputStream -import org.pgpainless.PGPainless.Companion.getPolicy -import org.pgpainless.algorithm.CompressionAlgorithm -import org.pgpainless.algorithm.SymmetricKeyAlgorithm -import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity +import org.pgpainless.PGPainless import org.pgpainless.util.NullOutputStream -import org.slf4j.Logger -import org.slf4j.LoggerFactory -class EncryptionBuilder : EncryptionBuilderInterface { +class EncryptionBuilder(private val api: PGPainless) : EncryptionBuilderInterface { override fun onOutputStream( outputStream: OutputStream ): EncryptionBuilderInterface.WithOptions { - return WithOptionsImpl(outputStream) + return WithOptionsImpl(outputStream, api) } override fun discardOutput(): EncryptionBuilderInterface.WithOptions { return onOutputStream(NullOutputStream()) } - class WithOptionsImpl(val outputStream: OutputStream) : EncryptionBuilderInterface.WithOptions { + class WithOptionsImpl(val outputStream: OutputStream, private val api: PGPainless) : + EncryptionBuilderInterface.WithOptions { override fun withOptions(options: ProducerOptions): EncryptionStream { - return EncryptionStream(outputStream, options) - } - } - - companion object { - - @JvmStatic val LOGGER: Logger = LoggerFactory.getLogger(EncryptionBuilder::class.java) - - /** - * Negotiate the [SymmetricKeyAlgorithm] used for message encryption. - * - * @param encryptionOptions encryption options - * @return negotiated symmetric key algorithm - */ - @JvmStatic - fun negotiateSymmetricEncryptionAlgorithm( - encryptionOptions: EncryptionOptions - ): SymmetricKeyAlgorithm { - val preferences = - encryptionOptions.keyViews.values - .map { it.preferredSymmetricKeyAlgorithms } - .toList() - val algorithm = - byPopularity() - .negotiate( - getPolicy().symmetricKeyEncryptionAlgorithmPolicy, - encryptionOptions.encryptionAlgorithmOverride, - preferences) - LOGGER.debug( - "Negotiation resulted in {} being the symmetric encryption algorithm of choice.", - algorithm) - return algorithm - } - - @JvmStatic - fun negotiateCompressionAlgorithm(producerOptions: ProducerOptions): CompressionAlgorithm { - return producerOptions.compressionAlgorithmOverride + return EncryptionStream(outputStream, options, api) } } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt index 957d08af..b37c854f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt @@ -10,11 +10,12 @@ import org.bouncycastle.openpgp.api.OpenPGPCertificate import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey import org.bouncycastle.openpgp.api.OpenPGPImplementation import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator +import org.pgpainless.PGPainless import org.pgpainless.PGPainless.Companion.inspectKeyRing import org.pgpainless.algorithm.EncryptionPurpose import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity import org.pgpainless.authentication.CertificateAuthority -import org.pgpainless.bouncycastle.extensions.toOpenPGPCertificate import org.pgpainless.encryption_signing.EncryptionOptions.EncryptionKeySelector import org.pgpainless.exception.KeyException.* import org.pgpainless.key.SubkeyIdentifier @@ -22,7 +23,10 @@ import org.pgpainless.key.info.KeyAccessor import org.pgpainless.key.info.KeyRingInfo import org.pgpainless.util.Passphrase -class EncryptionOptions(private val purpose: EncryptionPurpose) { +class EncryptionOptions( + private val purpose: EncryptionPurpose, + private val api: PGPainless = PGPainless.getInstance() +) { private val _encryptionMethods: MutableSet = mutableSetOf() private val _encryptionKeys: MutableSet = mutableSetOf() private val _encryptionKeyIdentifiers: MutableSet = mutableSetOf() @@ -87,7 +91,7 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) { .lookupByUserId(userId, email, evaluationDate, targetAmount) .filter { it.isAuthenticated() } .forEach { - addRecipient(it.certificate.toOpenPGPCertificate()).also { foundAcceptable = true } + addRecipient(api.toCertificate(it.certificate)).also { foundAcceptable = true } } require(foundAcceptable) { "Could not identify any trust-worthy certificates for '$userId' and target trust amount $targetAmount." @@ -225,7 +229,7 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) { key: PGPPublicKeyRing, userId: CharSequence, encryptionKeySelector: EncryptionKeySelector - ) = addRecipient(key.toOpenPGPCertificate(), userId, encryptionKeySelector) + ) = addRecipient(api.toCertificate(key), userId, encryptionKeySelector) /** * Encrypt the message for the given recipients [OpenPGPCertificate], filtering encryption @@ -251,7 +255,7 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) { replaceWith = ReplaceWith("addRecipient(key.toOpenPGPCertificate(), encryptionKeySelector)")) fun addRecipient(key: PGPPublicKeyRing, encryptionKeySelector: EncryptionKeySelector) = - addRecipient(key.toOpenPGPCertificate(), encryptionKeySelector) + addRecipient(api.toCertificate(key), encryptionKeySelector) /** * Encrypt the message for the recipients [OpenPGPCertificate], keeping the recipient anonymous @@ -282,7 +286,7 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) { fun addHiddenRecipient( key: PGPPublicKeyRing, selector: EncryptionKeySelector = encryptionKeySelector - ) = addHiddenRecipient(key.toOpenPGPCertificate(), selector) + ) = addHiddenRecipient(api.toCertificate(key), selector) private fun addAsRecipient( cert: OpenPGPCertificate, @@ -406,6 +410,17 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) { fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty() + internal fun negotiateSymmetricEncryptionAlgorithm(): SymmetricKeyAlgorithm { + val preferences = keyViews.values.map { it.preferredSymmetricKeyAlgorithms }.toList() + val algorithm = + byPopularity() + .negotiate( + api.algorithmPolicy.symmetricKeyEncryptionAlgorithmPolicy, + encryptionAlgorithmOverride, + preferences) + return algorithm + } + fun interface EncryptionKeySelector { fun selectEncryptionSubkeys( encryptionCapableKeys: List diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt index 0594d101..322cf24d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt @@ -13,13 +13,12 @@ import org.bouncycastle.openpgp.PGPCompressedDataGenerator import org.bouncycastle.openpgp.PGPEncryptedDataGenerator import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPLiteralDataGenerator -import org.bouncycastle.openpgp.api.OpenPGPImplementation +import org.pgpainless.PGPainless import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.StreamEncoding import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.util.ArmoredOutputStreamFactory -import org.slf4j.LoggerFactory // 1 << 8 causes wrong partial body length encoding // 1 << 9 fixes this. @@ -38,6 +37,7 @@ const val BUFFER_SIZE = 1 shl 9 class EncryptionStream( private var outermostStream: OutputStream, private val options: ProducerOptions, + private val api: PGPainless ) : OutputStream() { private val resultBuilder: EncryptionResult.Builder = EncryptionResult.builder() @@ -63,12 +63,10 @@ class EncryptionStream( private fun prepareArmor() { if (!options.isAsciiArmor) { - LOGGER.debug("Output will be unarmored.") return } outermostStream = BufferedOutputStream(outermostStream) - LOGGER.debug("Wrap encryption output in ASCII armor.") armorOutputStream = ArmoredOutputStreamFactory.get(outermostStream, options).also { outermostStream = it } } @@ -84,14 +82,13 @@ class EncryptionStream( "If EncryptionOptions are provided, at least one encryption method MUST be provided as well." } - EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(options.encryptionOptions).let { + options.encryptionOptions.negotiateSymmetricEncryptionAlgorithm().let { resultBuilder.setEncryptionAlgorithm(it) - LOGGER.debug("Encrypt message using symmetric algorithm $it.") val encryptedDataGenerator = PGPEncryptedDataGenerator( - OpenPGPImplementation.getInstance() - .pgpDataEncryptorBuilder(it.algorithmId) - .apply { setWithIntegrityPacket(true) }) + api.implementation.pgpDataEncryptorBuilder(it.algorithmId).apply { + setWithIntegrityPacket(true) + }) options.encryptionOptions.encryptionMethods.forEach { m -> encryptedDataGenerator.addMethod(m) } @@ -109,12 +106,11 @@ class EncryptionStream( @Throws(IOException::class) private fun prepareCompression() { - EncryptionBuilder.negotiateCompressionAlgorithm(options).let { + options.negotiateCompressionAlgorithm().let { resultBuilder.setCompressionAlgorithm(it) compressedDataGenerator = PGPCompressedDataGenerator(it.algorithmId) if (it == CompressionAlgorithm.UNCOMPRESSED) return - LOGGER.debug("Compress using $it.") basicCompressionStream = BCPGOutputStream(compressedDataGenerator!!.open(outermostStream)).also { stream -> outermostStream = stream @@ -267,8 +263,4 @@ class EncryptionStream( val isClosed get() = closed - - companion object { - @JvmStatic private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java) - } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt index e77ef2e3..ac48a65b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt @@ -10,10 +10,10 @@ import org.pgpainless.PGPainless import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.StreamEncoding -class ProducerOptions -private constructor( +class ProducerOptions( val encryptionOptions: EncryptionOptions?, - val signingOptions: SigningOptions? + val signingOptions: SigningOptions?, + val api: PGPainless = PGPainless.getInstance() ) { private var _fileName: String = "" @@ -25,7 +25,7 @@ private constructor( var isDisableAsciiArmorCRC = false private var _compressionAlgorithmOverride: CompressionAlgorithm = - PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm + api.algorithmPolicy.compressionAlgorithmPolicy.defaultCompressionAlgorithm private var asciiArmor = true private var _comment: String? = null private var _version: String? = null @@ -104,6 +104,13 @@ private constructor( */ fun hasVersion() = version != null + /** + * Configure the resulting OpenPGP message to make use of the Cleartext Signature Framework + * (CSF). A CSF message MUST be signed using detached signatures only and MUST NOT be encrypted. + * + * @see + * [RFC9580: OpenPGP - Cleartext Signature Framework](https://www.rfc-editor.org/rfc/rfc9580.html#name-cleartext-signature-framewo) + */ fun setCleartextSigned() = apply { require(signingOptions != null) { "Signing Options cannot be null if cleartext signing is enabled." @@ -230,6 +237,10 @@ private constructor( _hideArmorHeaders = hideArmorHeaders } + internal fun negotiateCompressionAlgorithm(): CompressionAlgorithm { + return compressionAlgorithmOverride + } + companion object { /** * Sign and encrypt some data. diff --git a/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestTwoSubkeysEncryption.java b/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestTwoSubkeysEncryption.java index 4b715a76..373396df 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestTwoSubkeysEncryption.java +++ b/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestTwoSubkeysEncryption.java @@ -17,7 +17,6 @@ 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.EncryptionPurpose; import org.pgpainless.encryption_signing.EncryptionOptions; import org.pgpainless.encryption_signing.EncryptionResult; import org.pgpainless.encryption_signing.EncryptionStream; @@ -51,7 +50,7 @@ public class TestTwoSubkeysEncryption { EncryptionStream encryptionStream = PGPainless.encryptAndOrSign() .onOutputStream(out) .withOptions( - ProducerOptions.encrypt(new EncryptionOptions(EncryptionPurpose.ANY) + ProducerOptions.encrypt(EncryptionOptions.get() .addRecipient(publicKeys, EncryptionOptions.encryptToAllCapableSubkeys()) ) .setAsciiArmor(false)