From aca884e9366b0e60654db4185db280fe262ac500 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 29 Sep 2023 14:57:48 +0200 Subject: [PATCH] Kotlin conversion: ArmoredOutputStreamFactory Also allow configuration of CRC calculation for both input and output streams --- .../util/ArmoredOutputStreamFactory.java | 132 ------------------ .../ConsumerOptions.kt | 1 + .../encryption_signing/ProducerOptions.kt | 1 + .../util/ArmoredInputStreamFactory.kt | 12 +- .../util/ArmoredOutputStreamFactory.kt | 102 ++++++++++++++ 5 files changed, 114 insertions(+), 134 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java deleted file mode 100644 index 269f8674..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util; - -import java.io.OutputStream; - -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.pgpainless.encryption_signing.ProducerOptions; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Factory to create configured {@link ArmoredOutputStream ArmoredOutputStreams}. - * The configuration entails setting custom version and comment headers. - */ -public final class ArmoredOutputStreamFactory { - - /** - * Name of the program. - */ - public static final String PGPAINLESS = "PGPainless"; - private static String version = PGPAINLESS; - private static String[] comment = new String[0]; - - private ArmoredOutputStreamFactory() { - - } - - private static ArmoredOutputStream.Builder getBuilder() { - ArmoredOutputStream.Builder builder = ArmoredOutputStream.builder(); - builder.clearHeaders(); - if (version != null && !version.isEmpty()) { - builder.setVersion(version); - } - for (String comment : comment) { - builder.addComment(comment); - } - return builder; - } - - /** - * Wrap an {@link OutputStream} inside a preconfigured {@link ArmoredOutputStream}. - * - * @param outputStream inner stream - * @return armored output stream - */ - @Nonnull - public static ArmoredOutputStream get(@Nonnull OutputStream outputStream) { - return getBuilder().build(outputStream); - } - - /** - * Return an instance of the {@link ArmoredOutputStream} which might have pre-populated armor headers. - * - * @param outputStream output stream - * @param options options - * @return armored output stream - */ - @Nonnull - public static ArmoredOutputStream get(@Nonnull OutputStream outputStream, @Nonnull ProducerOptions options) { - ArmoredOutputStream.Builder builder = getBuilder(); - if (options.isHideArmorHeaders()) { - builder.clearHeaders(); - } - if (options.hasVersion()) { - builder.setVersion(options.getVersion()); - } - if (options.hasComment()) { - builder.setComment(options.getComment()); - } - return builder.build(outputStream); - } - - /** - * Overwrite the version header of ASCII armors with a custom value. - * Newlines in the version info string result in multiple version header entries. - * If this is set to
null
, then the version header is omitted altogether. - * - * @param versionString version string - */ - public static void setVersionInfo(@Nullable String versionString) { - if (versionString == null) { - version = null; - return; - } - String trimmed = versionString.trim(); - if (trimmed.isEmpty()) { - version = null; - } else { - version = trimmed; - } - } - - /** - * Reset the version header to its default value of {@link #PGPAINLESS}. - */ - public static void resetVersionInfo() { - version = PGPAINLESS; - } - - /** - * Set a comment header value in the ASCII armor header. - * If the comment contains newlines, it will be split into multiple header entries. - * - * @see org.pgpainless.encryption_signing.ProducerOptions#setComment(String) for how to set comments for - * individual messages. - * - * @param commentString comment - */ - public static void setComment(@Nullable String commentString) { - if (commentString == null) { - throw new IllegalArgumentException("Comment cannot be null."); - } - String trimmed = commentString.trim(); - if (trimmed.isEmpty()) { - throw new IllegalArgumentException("Comment cannot be empty."); - } - - String[] lines = commentString.split("\n"); - comment = lines; - } - - /** - * Reset to the default of no comment headers. - */ - public static void resetComment() { - comment = new String[0]; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt index dbff0551..e0ec1fd5 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt @@ -24,6 +24,7 @@ import java.util.* class ConsumerOptions { private var ignoreMDCErrors = false + var isDisableAsciiArmorCRC = false private var forceNonOpenPgpData = false private var verifyNotBefore: Date? = null private var verifyNotAfter: Date? = Date() 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 bdc153e9..88345fd9 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 @@ -20,6 +20,7 @@ class ProducerOptions private constructor( private var applyCRLFEncoding = false private var cleartextSigned = false private var _hideArmorHeaders = false + var isDisableAsciiArmorCRC = false private var _compressionAlgorithmOverride: CompressionAlgorithm = PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm private var asciiArmor = true diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt index 82d8f248..254b5c88 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt @@ -5,6 +5,7 @@ package org.pgpainless.util import org.bouncycastle.bcpg.ArmoredInputStream +import org.pgpainless.decryption_verification.ConsumerOptions import java.io.IOException import java.io.InputStream @@ -24,12 +25,19 @@ class ArmoredInputStreamFactory { * @throws IOException in case of an IO error */ @JvmStatic + @JvmOverloads @Throws(IOException::class) - fun get(inputStream: InputStream): ArmoredInputStream { + fun get(inputStream: InputStream, options: ConsumerOptions? = null): ArmoredInputStream { return when (inputStream) { is CRCingArmoredInputStreamWrapper -> inputStream is ArmoredInputStream -> CRCingArmoredInputStreamWrapper(inputStream) - else -> CRCingArmoredInputStreamWrapper(ArmoredInputStream(inputStream)) + else -> CRCingArmoredInputStreamWrapper( + ArmoredInputStream.builder().apply { + setParseForHeaders(true) + options?.let { + setIgnoreCRC(it.isDisableAsciiArmorCRC) + } + }.build(inputStream)) } } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt new file mode 100644 index 00000000..69a5520f --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util + +import org.bouncycastle.bcpg.ArmoredOutputStream +import org.pgpainless.encryption_signing.ProducerOptions +import java.io.OutputStream + +/** + * Factory to create configured [ArmoredOutputStream] instances. + * The configuration entails setting custom version and comment headers. + */ +class ArmoredOutputStreamFactory { + + companion object { + private const val PGPAINLESS = "PGPainless" + + @JvmStatic + private var version: String? = PGPAINLESS + private var comment: String? = null + + /** + * Return an instance of the [ArmoredOutputStream] which might have pre-populated armor headers. + * + * @param outputStream output stream + * @param options options + * @return armored output stream + */ + @JvmStatic + @JvmOverloads + fun get(outputStream: OutputStream, options: ProducerOptions? = null): ArmoredOutputStream { + val builder = ArmoredOutputStream.builder().apply { + // set fields defined in ArmoredOutputStreamFactory + if (!version.isNullOrBlank()) setVersion(version) + if (!comment.isNullOrBlank()) setComment(comment) + + // set (and potentially overwrite with) values from ProducerOptions + options?.let { + enableCRC(!it.isDisableAsciiArmorCRC) + if (it.isHideArmorHeaders) clearHeaders() + if (it.hasVersion()) setVersion(it.version) + if (it.hasComment()) addComment(it.comment) + // TODO: configure CRC + } + } + return get(outputStream, builder) + } + + /** + * Build an [ArmoredOutputStream] around the given [outputStream], configured according to the passed in + * [ArmoredOutputStream.Builder] instance. + * + * @param outputStream output stream + * @param builder builder instance + */ + @JvmStatic + fun get(outputStream: OutputStream, builder: ArmoredOutputStream.Builder): ArmoredOutputStream { + return builder.build(outputStream) + } + + /** + * Overwrite the version header of ASCII armors with a custom value. + * Newlines in the version info string result in multiple version header entries. + * If this is set to
null
, then the version header is omitted altogether. + * + * @param versionString version string + */ + @JvmStatic + fun setVersionInfo(versionString: String?) { + version = if (versionString.isNullOrBlank()) null else versionString.trim() + } + + /** + * Reset the version header to its default value of [PGPAINLESS]. + */ + @JvmStatic + fun resetVersionInfo() { + version = PGPAINLESS + } + + /** + * Set a comment header value in the ASCII armor header. + * If the comment contains newlines, it will be split into multiple header entries. + * + * @see [ProducerOptions.setComment] for how to set comments for individual messages. + * + * @param commentString comment + */ + @JvmStatic + fun setComment(commentString: String) { + require(commentString.isNotBlank()) { "Comment cannot be empty. See resetComment() to clear the comment." } + comment = commentString.trim() + } + + @JvmStatic + fun resetComment() { + comment = null + } + } +} \ No newline at end of file