Allow setting custom version header when encrypting/signing message

This commit is contained in:
Paul Schaub 2023-06-05 20:18:06 +02:00
parent add1b89019
commit 41e663e25b
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
4 changed files with 109 additions and 7 deletions

View File

@ -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;
}

View File

@ -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'.
*
* <br>
* 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.
* <br>
* 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}.
*
* <br>
* 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.
*
* <br>
* 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))".

View File

@ -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}.
*

View File

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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"));
}
}