001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.encryption_signing;
006
007import java.util.Date;
008import javax.annotation.Nonnull;
009import javax.annotation.Nullable;
010
011import org.bouncycastle.openpgp.PGPLiteralData;
012import org.pgpainless.PGPainless;
013import org.pgpainless.algorithm.CompressionAlgorithm;
014import org.pgpainless.algorithm.StreamEncoding;
015
016public final class ProducerOptions {
017
018    private final EncryptionOptions encryptionOptions;
019    private final SigningOptions signingOptions;
020    private String fileName = "";
021    private Date modificationDate = PGPLiteralData.NOW;
022    private StreamEncoding streamEncoding = StreamEncoding.BINARY;
023    private boolean cleartextSigned = false;
024
025    private CompressionAlgorithm compressionAlgorithmOverride = PGPainless.getPolicy().getCompressionAlgorithmPolicy()
026            .defaultCompressionAlgorithm();
027    private boolean asciiArmor = true;
028
029    private ProducerOptions(EncryptionOptions encryptionOptions, SigningOptions signingOptions) {
030        this.encryptionOptions = encryptionOptions;
031        this.signingOptions = signingOptions;
032    }
033
034    /**
035     * Sign and encrypt some data.
036     *
037     * @param encryptionOptions encryption options
038     * @param signingOptions signing options
039     * @return builder
040     */
041    public static ProducerOptions signAndEncrypt(EncryptionOptions encryptionOptions,
042                                                 SigningOptions signingOptions) {
043        throwIfNull(encryptionOptions);
044        throwIfNull(signingOptions);
045        return new ProducerOptions(encryptionOptions, signingOptions);
046    }
047
048    /**
049     * Sign some data without encryption.
050     *
051     * @param signingOptions signing options
052     * @return builder
053     */
054    public static ProducerOptions sign(SigningOptions signingOptions) {
055        throwIfNull(signingOptions);
056        return new ProducerOptions(null, signingOptions);
057    }
058
059    /**
060     * Encrypt some data without signing.
061     *
062     * @param encryptionOptions encryption options
063     * @return builder
064     */
065    public static ProducerOptions encrypt(EncryptionOptions encryptionOptions) {
066        throwIfNull(encryptionOptions);
067        return new ProducerOptions(encryptionOptions, null);
068    }
069
070    public static ProducerOptions noEncryptionNoSigning() {
071        return new ProducerOptions(null, null);
072    }
073
074    private static void throwIfNull(EncryptionOptions encryptionOptions) {
075        if (encryptionOptions == null) {
076            throw new NullPointerException("EncryptionOptions cannot be null.");
077        }
078    }
079
080    private static void throwIfNull(SigningOptions signingOptions) {
081        if (signingOptions == null) {
082            throw new NullPointerException("SigningOptions cannot be null.");
083        }
084    }
085
086    /**
087     * Specify, whether the result of the encryption/signing operation shall be ascii armored.
088     * The default value is true.
089     *
090     * @param asciiArmor ascii armor
091     * @return builder
092     */
093    public ProducerOptions setAsciiArmor(boolean asciiArmor) {
094        if (cleartextSigned && !asciiArmor) {
095            throw new IllegalArgumentException("Cleartext signing is enabled. Cannot disable ASCII armoring.");
096        }
097        this.asciiArmor = asciiArmor;
098        return this;
099    }
100
101    /**
102     * Return true if the output of the encryption/signing operation shall be ascii armored.
103     *
104     * @return ascii armored
105     */
106    public boolean isAsciiArmor() {
107        return asciiArmor;
108    }
109
110    public ProducerOptions setCleartextSigned() {
111        if (signingOptions == null) {
112            throw new IllegalArgumentException("Signing Options cannot be null if cleartext signing is enabled.");
113        }
114        if (encryptionOptions != null) {
115            throw new IllegalArgumentException("Cannot encode encrypted message as Cleartext Signed.");
116        }
117        for (SigningOptions.SigningMethod method : signingOptions.getSigningMethods().values()) {
118            if (!method.isDetached()) {
119                throw new IllegalArgumentException("For cleartext signed message, all signatures must be added as detached signatures.");
120            }
121        }
122        cleartextSigned = true;
123        asciiArmor = true;
124        compressionAlgorithmOverride = CompressionAlgorithm.UNCOMPRESSED;
125        return this;
126    }
127
128    public boolean isCleartextSigned() {
129        return cleartextSigned;
130    }
131
132    /**
133     * Set the name of the encrypted file.
134     * Note: This option cannot be used simultaneously with {@link #setForYourEyesOnly()}.
135     *
136     * @param fileName name of the encrypted file
137     * @return this
138     */
139    public ProducerOptions setFileName(@Nonnull String fileName) {
140        this.fileName = fileName;
141        return this;
142    }
143
144    /**
145     * Return the encrypted files name.
146     *
147     * @return file name
148     */
149    public String getFileName() {
150        return fileName;
151    }
152
153    /**
154     * Mark the encrypted message as for-your-eyes-only by setting a special file name.
155     * Note: Therefore this method cannot be used simultaneously with {@link #setFileName(String)}.
156     *
157     * @return this
158     */
159    public ProducerOptions setForYourEyesOnly() {
160        this.fileName = PGPLiteralData.CONSOLE;
161        return this;
162    }
163
164    /**
165     * Set the modification date of the encrypted file.
166     *
167     * @param modificationDate Modification date of the encrypted file.
168     * @return this
169     */
170    public ProducerOptions setModificationDate(@Nonnull Date modificationDate) {
171        this.modificationDate = modificationDate;
172        return this;
173    }
174
175    /**
176     * Return the modification date of the encrypted file.
177     *
178     * @return modification date
179     */
180    public Date getModificationDate() {
181        return modificationDate;
182    }
183
184    /**
185     * Set the format of the literal data packet.
186     * Defaults to {@link StreamEncoding#BINARY}.
187     *
188     * @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.9">RFC4880 ยง5.9. Literal Data Packet</a>
189     *
190     * @param encoding encoding
191     * @return this
192     */
193    public ProducerOptions setEncoding(@Nonnull StreamEncoding encoding) {
194        this.streamEncoding = encoding;
195        return this;
196    }
197
198    public StreamEncoding getEncoding() {
199        return streamEncoding;
200    }
201    /**
202     * Override which compression algorithm shall be used.
203     *
204     * @param compressionAlgorithm compression algorithm override
205     * @return builder
206     */
207    public ProducerOptions overrideCompressionAlgorithm(CompressionAlgorithm compressionAlgorithm) {
208        if (compressionAlgorithm == null) {
209            throw new NullPointerException("Compression algorithm cannot be null.");
210        }
211        this.compressionAlgorithmOverride = compressionAlgorithm;
212        return this;
213    }
214
215    public CompressionAlgorithm getCompressionAlgorithmOverride() {
216        return compressionAlgorithmOverride;
217    }
218
219    public @Nullable EncryptionOptions getEncryptionOptions() {
220        return encryptionOptions;
221    }
222
223    public @Nullable SigningOptions getSigningOptions() {
224        return signingOptions;
225    }
226}