pgpainless/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java

264 lines
10 KiB
Java
Raw Normal View History

/*
* Copyright 2018 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.encryption_signing;
2018-06-04 19:45:18 +02:00
import java.io.IOException;
import java.io.OutputStream;
2018-06-21 15:19:07 +02:00
import java.util.logging.Level;
import java.util.logging.Logger;
2020-08-24 16:26:29 +02:00
import javax.annotation.Nonnull;
2018-06-04 19:45:18 +02:00
2018-06-05 01:30:58 +02:00
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
2020-12-27 01:56:18 +01:00
import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
2021-05-06 00:04:03 +02:00
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
2020-12-27 01:56:18 +01:00
import org.pgpainless.implementation.ImplementationFactory;
2021-04-26 13:38:12 +02:00
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.util.ArmoredOutputStreamFactory;
2018-06-04 19:45:18 +02:00
2018-06-05 01:30:58 +02:00
/**
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
* @see <a href="https://github.com/neuhalje/bouncy-gpg/blob/master/src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/PGPEncryptingStream.java">Source</a>
*/
2018-07-02 21:40:59 +02:00
public final class EncryptionStream extends OutputStream {
2018-06-04 19:45:18 +02:00
2018-06-21 15:19:07 +02:00
private static final Logger LOGGER = Logger.getLogger(EncryptionStream.class.getName());
private static final Level LEVEL = Level.FINE;
2021-05-06 00:04:03 +02:00
private final ProducerOptions options;
private final EncryptionResult.Builder resultBuilder = EncryptionResult.builder();
2018-06-05 01:30:58 +02:00
private boolean closed = false;
2021-05-06 00:04:03 +02:00
private static final int BUFFER_SIZE = 1 << 8;
2018-06-05 01:30:58 +02:00
2021-05-06 00:04:03 +02:00
OutputStream outermostStream;
2020-01-10 15:43:22 +01:00
private ArmoredOutputStream armorOutputStream = null;
2018-06-05 01:30:58 +02:00
private OutputStream publicKeyEncryptedStream = null;
private PGPCompressedDataGenerator compressedDataGenerator;
private BCPGOutputStream basicCompressionStream;
private PGPLiteralDataGenerator literalDataGenerator;
private OutputStream literalDataStream;
EncryptionStream(@Nonnull OutputStream targetOutputStream,
@Nonnull ProducerOptions options)
2018-06-05 01:30:58 +02:00
throws IOException, PGPException {
2021-05-06 00:04:03 +02:00
this.options = options;
2020-01-10 15:43:22 +01:00
outermostStream = targetOutputStream;
2021-05-06 00:04:03 +02:00
2020-01-10 15:43:22 +01:00
prepareArmor();
prepareEncryption();
prepareCompression();
prepareOnePassSignatures();
prepareLiteralDataProcessing();
2020-01-10 15:43:22 +01:00
}
private void prepareArmor() {
2021-05-06 00:04:03 +02:00
if (!options.isAsciiArmor()) {
2018-06-21 15:19:07 +02:00
LOGGER.log(LEVEL, "Encryption output will be binary");
2020-01-10 15:43:22 +01:00
return;
2018-06-05 01:30:58 +02:00
}
2020-01-10 15:43:22 +01:00
LOGGER.log(LEVEL, "Wrap encryption output in ASCII armor");
armorOutputStream = ArmoredOutputStreamFactory.get(outermostStream);
2020-01-10 15:43:22 +01:00
outermostStream = armorOutputStream;
}
2018-06-05 01:30:58 +02:00
2020-01-10 15:43:22 +01:00
private void prepareEncryption() throws IOException, PGPException {
2021-05-06 00:04:03 +02:00
EncryptionOptions encryptionOptions = options.getEncryptionOptions();
if (encryptionOptions == null || encryptionOptions.getEncryptionMethods().isEmpty()) {
// No encryption options/methods -> no encryption
resultBuilder.setEncryptionAlgorithm(SymmetricKeyAlgorithm.NULL);
2020-01-10 15:43:22 +01:00
return;
}
2018-06-05 01:30:58 +02:00
2021-05-06 00:04:03 +02:00
SymmetricKeyAlgorithm encryptionAlgorithm = EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(encryptionOptions);
resultBuilder.setEncryptionAlgorithm(encryptionAlgorithm);
LOGGER.log(LEVEL, "Encrypt message using " + encryptionAlgorithm);
2020-12-27 01:56:18 +01:00
PGPDataEncryptorBuilder dataEncryptorBuilder =
2021-05-06 00:04:03 +02:00
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(encryptionAlgorithm);
2021-06-10 12:41:12 +02:00
dataEncryptorBuilder.setWithIntegrityPacket(true);
2018-06-05 01:30:58 +02:00
2020-01-10 15:43:22 +01:00
PGPEncryptedDataGenerator encryptedDataGenerator =
new PGPEncryptedDataGenerator(dataEncryptorBuilder);
2021-05-06 00:04:03 +02:00
for (PGPKeyEncryptionMethodGenerator encryptionMethod : encryptionOptions.getEncryptionMethods()) {
encryptedDataGenerator.addMethod(encryptionMethod);
2018-06-05 01:30:58 +02:00
}
2021-05-06 00:04:03 +02:00
for (SubkeyIdentifier recipientSubkeyIdentifier : encryptionOptions.getEncryptionKeyIdentifiers()) {
resultBuilder.addRecipient(recipientSubkeyIdentifier);
}
2020-01-10 15:43:22 +01:00
publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, new byte[BUFFER_SIZE]);
outermostStream = publicKeyEncryptedStream;
}
private void prepareCompression() throws IOException {
2021-05-25 13:52:45 +02:00
CompressionAlgorithm compressionAlgorithm = EncryptionBuilder.negotiateCompressionAlgorithm(options);
2021-05-06 00:04:03 +02:00
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
2018-06-05 01:30:58 +02:00
compressedDataGenerator = new PGPCompressedDataGenerator(
compressionAlgorithm.getAlgorithmId());
if (compressionAlgorithm == CompressionAlgorithm.UNCOMPRESSED) {
return;
}
LOGGER.log(LEVEL, "Compress using " + compressionAlgorithm);
2020-01-10 15:43:22 +01:00
basicCompressionStream = new BCPGOutputStream(compressedDataGenerator.open(outermostStream));
outermostStream = basicCompressionStream;
2020-01-10 15:43:22 +01:00
}
2018-06-05 01:30:58 +02:00
2020-01-10 15:43:22 +01:00
private void prepareOnePassSignatures() throws IOException, PGPException {
2021-05-06 00:04:03 +02:00
SigningOptions signingOptions = options.getSigningOptions();
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
// No singing options/methods -> no signing
return;
}
for (SubkeyIdentifier identifier : signingOptions.getSigningMethods().keySet()) {
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(identifier);
if (!signingMethod.isDetached()) {
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
signatureGenerator.generateOnePassVersion(false).encode(outermostStream);
}
2018-06-05 01:30:58 +02:00
}
2020-01-10 15:43:22 +01:00
}
2018-06-05 01:30:58 +02:00
private void prepareLiteralDataProcessing() throws IOException {
2018-06-05 01:30:58 +02:00
literalDataGenerator = new PGPLiteralDataGenerator();
literalDataStream = literalDataGenerator.open(outermostStream,
options.getEncoding().getCode(),
options.getFileName(),
options.getModificationDate(),
new byte[BUFFER_SIZE]);
outermostStream = literalDataStream;
resultBuilder.setFileName(options.getFileName())
.setModificationDate(options.getModificationDate())
.setFileEncoding(options.getEncoding());
2018-06-04 19:45:18 +02:00
}
@Override
2018-06-05 01:30:58 +02:00
public void write(int data) throws IOException {
outermostStream.write(data);
2021-05-06 00:04:03 +02:00
SigningOptions signingOptions = options.getSigningOptions();
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
return;
}
2018-06-05 01:30:58 +02:00
2021-05-06 00:04:03 +02:00
for (SubkeyIdentifier signingKey : signingOptions.getSigningMethods().keySet()) {
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
2018-06-05 01:30:58 +02:00
byte asByte = (byte) (data & 0xff);
signatureGenerator.update(asByte);
}
}
@Override
2021-05-06 00:04:03 +02:00
public void write(@Nonnull byte[] buffer) throws IOException {
2018-06-05 01:30:58 +02:00
write(buffer, 0, buffer.length);
}
@Override
2021-05-06 00:04:03 +02:00
public void write(@Nonnull byte[] buffer, int off, int len) throws IOException {
outermostStream.write(buffer, 0, len);
2021-05-06 00:04:03 +02:00
SigningOptions signingOptions = options.getSigningOptions();
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
return;
}
for (SubkeyIdentifier signingKey : signingOptions.getSigningMethods().keySet()) {
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
2018-06-05 01:30:58 +02:00
signatureGenerator.update(buffer, 0, len);
}
}
@Override
public void flush() throws IOException {
outermostStream.flush();
2018-06-05 01:30:58 +02:00
}
@Override
public void close() throws IOException {
2020-01-10 15:43:22 +01:00
if (closed) {
return;
}
2018-06-04 19:45:18 +02:00
2020-01-10 15:43:22 +01:00
// Literal Data
literalDataStream.flush();
literalDataStream.close();
literalDataGenerator.close();
2018-06-05 01:30:58 +02:00
try {
writeSignatures();
} catch (PGPException e) {
throw new IOException("Exception while writing signatures.", e);
}
2018-06-05 01:30:58 +02:00
2020-01-10 15:43:22 +01:00
// Compressed Data
compressedDataGenerator.close();
// Public Key Encryption
if (publicKeyEncryptedStream != null) {
publicKeyEncryptedStream.flush();
publicKeyEncryptedStream.close();
}
2018-06-05 01:30:58 +02:00
2020-01-10 15:43:22 +01:00
// Armor
if (armorOutputStream != null) {
armorOutputStream.flush();
armorOutputStream.close();
}
closed = true;
}
private void writeSignatures() throws PGPException, IOException {
2021-05-06 00:04:03 +02:00
SigningOptions signingOptions = options.getSigningOptions();
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
return;
}
for (SubkeyIdentifier signingKey : signingOptions.getSigningMethods().keySet()) {
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
PGPSignature signature = signatureGenerator.generate();
2021-05-06 00:04:03 +02:00
if (signingMethod.isDetached()) {
resultBuilder.addDetachedSignature(signingKey, signature);
} else {
signature.encode(outermostStream);
2018-06-05 01:30:58 +02:00
}
}
2018-06-04 19:45:18 +02:00
}
2021-05-06 00:04:03 +02:00
public EncryptionResult getResult() {
if (!closed) {
throw new IllegalStateException("EncryptionStream must be closed before accessing the Result.");
}
return resultBuilder.build();
}
public boolean isClosed() {
return closed;
}
2018-06-04 19:45:18 +02:00
}