diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java
index 2e370b94..d2a16e08 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java
@@ -99,6 +99,12 @@ public final class EncryptionStream extends OutputStream {
}
}
}
+ if (options.hasVersion()) {
+ String version = options.getVersion().trim();
+ if (!version.isEmpty()) {
+ ArmorUtils.setVersionHeader(armorOutputStream, version);
+ }
+ }
outermostStream = armorOutputStream;
}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/ProducerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/ProducerOptions.java
index 4948a7fe..00fbb10a 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/ProducerOptions.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/ProducerOptions.java
@@ -28,6 +28,7 @@ public final class ProducerOptions {
.defaultCompressionAlgorithm();
private boolean asciiArmor = true;
private String comment = null;
+ private String version = null;
private ProducerOptions(EncryptionOptions encryptionOptions, SigningOptions signingOptions) {
this.encryptionOptions = encryptionOptions;
@@ -120,7 +121,7 @@ public final class ProducerOptions {
* Set the comment header in ASCII armored output.
* The default value is null, which means no comment header is added.
* Multiline comments are possible using '\\n'.
- *
+ *
* Note: If a default header comment is set using {@link org.pgpainless.util.ArmoredOutputStreamFactory#setComment(String)},
* then both comments will be written to the produced ASCII armor.
*
@@ -128,13 +129,25 @@ public final class ProducerOptions {
* @return builder
*/
public ProducerOptions setComment(String comment) {
- if (!asciiArmor) {
- throw new IllegalArgumentException("Comment can only be set when ASCII armoring is enabled.");
- }
this.comment = comment;
return this;
}
+ /**
+ * Set the version header in ASCII armored output.
+ * The default value is null, which means no version header is added.
+ *
+ * Note: If the value is non-null, then this method overrides the default version header set using
+ * {@link org.pgpainless.util.ArmoredOutputStreamFactory#setVersionInfo(String)}.
+ *
+ * @param version version header, or null for no version info.
+ * @return builder
+ */
+ public ProducerOptions setVersion(String version) {
+ this.version = version;
+ return this;
+ }
+
/**
* Return comment set for header in ascii armored output.
*
@@ -144,15 +157,33 @@ public final class ProducerOptions {
return comment;
}
+ /**
+ * Return the version info header in ascii armored output.
+ *
+ * @return version info
+ */
+ public String getVersion() {
+ return version;
+ }
+
/**
* Return whether a comment was set (!= null).
*
- * @return comment
+ * @return true if commend is set
*/
public boolean hasComment() {
return comment != null;
}
+ /**
+ * Return whether a version header was set (!= null).
+ *
+ * @return true if version header is set
+ */
+ public boolean hasVersion() {
+ return version != null;
+ }
+
public ProducerOptions setCleartextSigned() {
if (signingOptions == null) {
throw new IllegalArgumentException("Signing Options cannot be null if cleartext signing is enabled.");
@@ -233,7 +264,7 @@ public final class ProducerOptions {
/**
* Set format metadata field of the literal data packet.
* Defaults to {@link StreamEncoding#BINARY}.
- *
+ *
* This does not change the encoding of the wrapped data itself.
* To apply CR/LF encoding to your input data before processing, use {@link #applyCRLFEncoding()} instead.
*
@@ -257,7 +288,7 @@ public final class ProducerOptions {
/**
* Apply special encoding of line endings to the input data.
* By default, this is disabled, which means that the data is not altered.
- *
+ *
* Enabling it will change the line endings to CR/LF.
* Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will result in
* the identity "decrypt(encrypt(data)) == data == verify(sign(data))".
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java b/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java
index 87170638..cf19d273 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java
@@ -417,6 +417,22 @@ public final class ArmorUtils {
return new Tuple<>(printed, countIdentities);
}
+ /**
+ * Set the version header entry in the ASCII armor.
+ * If the version info is null or only contains whitespace characters, then the version header will be removed.
+ *
+ * @param armor armored output stream
+ * @param version version header.
+ */
+ public static void setVersionHeader(@Nonnull ArmoredOutputStream armor,
+ @Nullable String version) {
+ if (version == null || version.trim().isEmpty()) {
+ armor.setHeader(HEADER_VERSION, null);
+ } else {
+ armor.setHeader(HEADER_VERSION, version);
+ }
+ }
+
/**
* Add an ASCII armor header entry about the used hash algorithm into the {@link ArmoredOutputStream}.
*
diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/AsciiArmorTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/AsciiArmorTest.java
new file mode 100644
index 00000000..b6f8bf24
--- /dev/null
+++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/AsciiArmorTest.java
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: 2023 Paul Schaub
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package org.pgpainless.encryption_signing;
+
+import org.bouncycastle.openpgp.PGPException;
+import org.junit.jupiter.api.Test;
+import org.pgpainless.PGPainless;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AsciiArmorTest {
+
+ @Test
+ public void testCustomAsciiArmorComments() throws PGPException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
+ .onOutputStream(out)
+ .withOptions(ProducerOptions.noEncryptionNoSigning()
+ .setAsciiArmor(true)
+ .setComment("This is a comment.\nThis is another comment."));
+ encryptionStream.write("Hello, World!".getBytes(StandardCharsets.UTF_8));
+ encryptionStream.close();
+
+ String asciiArmored = out.toString();
+ assertTrue(asciiArmored.contains("Comment: This is a comment."));
+ assertTrue(asciiArmored.contains("Comment: This is another comment."));
+ }
+
+ @Test
+ public void testCustomAsciiArmorVersion() throws IOException, PGPException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
+ .onOutputStream(out)
+ .withOptions(ProducerOptions.noEncryptionNoSigning()
+ .setAsciiArmor(true)
+ .setVersion("Custom-PGP 1.2.3"));
+ encryptionStream.write("Hello, World!".getBytes(StandardCharsets.UTF_8));
+ encryptionStream.close();
+
+ String asciiArmored = out.toString();
+ assertTrue(asciiArmored.contains("Version: Custom-PGP 1.2.3"));
+ }
+}