From 0d15332240387a7d0a0859c47c5efde2c19adec3 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 26 Apr 2024 14:32:47 +0200 Subject: [PATCH] Improve encryption support in FileInputStreamCache --- .../camel/spi/StreamCachingStrategy.java | 8 ++++ .../engine/DefaultStreamCachingStrategy.java | 12 ++++++ .../stream/CachedOutputStreamTest.java | 7 ++-- .../converter/stream/CachedOutputStream.java | 1 + .../camel/converter/stream/CipherPair.java | 39 +++++++++++++------ .../stream/FileInputStreamCache.java | 34 ++++------------ 6 files changed, 59 insertions(+), 42 deletions(-) diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java b/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java index 267949c9ba6e6..9ccfeaf67153f 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java @@ -23,6 +23,7 @@ import org.apache.camel.Message; import org.apache.camel.StaticService; import org.apache.camel.StreamCache; +import org.apache.camel.support.jsse.SecureRandomParameters; /** * Strategy for using stream caching. @@ -224,6 +225,13 @@ interface SpoolRule { String getSpoolCipher(); + /** + * Sets the parameters used to create a secure random when using encryption. + */ + void setSecureRandomParameters(SecureRandomParameters secureRandomParameters); + + SecureRandomParameters getSecureRandomParameters(); + /** * Whether to remove the temporary directory when stopping. *

diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java index 92df0417bd80a..cea5c3fed7204 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java @@ -31,6 +31,7 @@ import org.apache.camel.Message; import org.apache.camel.StreamCache; import org.apache.camel.spi.StreamCachingStrategy; +import org.apache.camel.support.jsse.SecureRandomParameters; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.FilePathResolver; import org.apache.camel.util.FileUtil; @@ -58,6 +59,7 @@ public class DefaultStreamCachingStrategy extends ServiceSupport implements Came private int spoolUsedHeapMemoryThreshold; private SpoolUsedHeapMemoryLimit spoolUsedHeapMemoryLimit; private String spoolCipher; + private SecureRandomParameters secureRandomParameters; private int bufferSize = IOHelper.DEFAULT_BUFFER_SIZE; private boolean removeSpoolDirectoryWhenStopping = true; private final UtilizationStatistics statistics = new UtilizationStatistics(); @@ -177,6 +179,16 @@ public void setSpoolCipher(String spoolCipher) { this.spoolCipher = spoolCipher; } + @Override + public SecureRandomParameters getSecureRandomParameters() { + return secureRandomParameters; + } + + @Override + public void setSecureRandomParameters(SecureRandomParameters secureRandomParameters) { + this.secureRandomParameters = secureRandomParameters; + } + @Override public int getBufferSize() { return bufferSize; diff --git a/core/camel-core/src/test/java/org/apache/camel/converter/stream/CachedOutputStreamTest.java b/core/camel-core/src/test/java/org/apache/camel/converter/stream/CachedOutputStreamTest.java index a71bbdc5823e0..9ccc3c8461d7c 100644 --- a/core/camel-core/src/test/java/org/apache/camel/converter/stream/CachedOutputStreamTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/converter/stream/CachedOutputStreamTest.java @@ -19,6 +19,7 @@ import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -153,13 +154,13 @@ public void testCacheStreamToFileAndCloseStream() throws Exception { @Test public void testCacheStreamToFileAndCloseStreamEncrypted() throws Exception { // set some stream or 8-bit block cipher transformation name - context.getStreamCachingStrategy().setSpoolCipher("RC4"); + context.getStreamCachingStrategy().setSpoolCipher("AES/CBC/PKCS5Padding"); context.start(); CachedOutputStream cos = new CachedOutputStream(exchange); cos.write(TEST_STRING.getBytes(StandardCharsets.UTF_8)); - cos.flush(); + cos.close(); File file = testDirectory().toFile(); String[] files = file.list(); @@ -167,7 +168,7 @@ public void testCacheStreamToFileAndCloseStreamEncrypted() throws Exception { assertEquals(1, files.length, "we should have a temp file"); assertTrue(new File(file, files[0]).length() > 10, "The content is written"); - java.io.FileInputStream tmpin = new java.io.FileInputStream(new File(file, files[0])); + FileInputStream tmpin = new FileInputStream(new File(file, files[0])); String temp = toString(tmpin); assertTrue(!temp.isEmpty() && !temp.contains("aaa"), "The content is not encrypted"); tmpin.close(); diff --git a/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java b/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java index 49a8c9d7f0e46..f178e1976de2c 100644 --- a/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java +++ b/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java @@ -155,6 +155,7 @@ private void pageToFileStream() throws IOException { // creates a tmp file and a file output stream currentStream = tempFileManager.createOutputStream(strategy); bout.writeTo(currentStream); + currentStream.flush(); } finally { // ensure flag is flipped to file based inMemory = false; diff --git a/core/camel-support/src/main/java/org/apache/camel/converter/stream/CipherPair.java b/core/camel-support/src/main/java/org/apache/camel/converter/stream/CipherPair.java index 4a8cf3197672c..b94a310698e83 100644 --- a/core/camel-support/src/main/java/org/apache/camel/converter/stream/CipherPair.java +++ b/core/camel-support/src/main/java/org/apache/camel/converter/stream/CipherPair.java @@ -16,44 +16,59 @@ */ package org.apache.camel.converter.stream; +import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.Key; import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; -import javax.crypto.spec.IvParameterSpec; +import org.apache.camel.support.jsse.SecureRandomParameters; import org.apache.camel.util.StringHelper; /** * A class to hold a pair of encryption and decryption ciphers. */ public class CipherPair { + private final String transformation; - private final Cipher enccipher; + private final SecureRandom random; private final Key key; - private final byte[] ivp; + private final AlgorithmParameterSpec params; - public CipherPair(String transformation) throws GeneralSecurityException { + public CipherPair(String transformation, SecureRandomParameters secureRandomParameters) throws GeneralSecurityException { this.transformation = transformation; + this.random = secureRandomParameters != null + ? secureRandomParameters.createSecureRandom() + : new SecureRandom(); String a = StringHelper.before(transformation, "/", transformation); KeyGenerator keygen = KeyGenerator.getInstance(a); - keygen.init(new SecureRandom()); + keygen.init(random); key = keygen.generateKey(); - enccipher = Cipher.getInstance(transformation); - enccipher.init(Cipher.ENCRYPT_MODE, key); - ivp = enccipher.getIV(); + + Cipher cipher = Cipher.getInstance(transformation); + cipher.init(Cipher.ENCRYPT_MODE, key, random); + AlgorithmParameters parameters = cipher.getParameters(); + params = parameters != null ? parameters.getParameterSpec(AlgorithmParameterSpec.class) : null; } public String getTransformation() { return transformation; } - public Cipher getEncryptor() { - return enccipher; + public Cipher createEncryptor() { + try { + Cipher enccipher = Cipher.getInstance(transformation); + enccipher.init(Cipher.ENCRYPT_MODE, key, params, random); + return enccipher; + } catch (GeneralSecurityException e) { + // should not happen + throw new IllegalStateException("Could not instantiate encryptor", e); + } } /** @@ -63,11 +78,11 @@ public Cipher getEncryptor() { public Cipher createDecryptor() { try { Cipher deccipher = Cipher.getInstance(transformation); - deccipher.init(Cipher.DECRYPT_MODE, key, ivp == null ? null : new IvParameterSpec(ivp)); + deccipher.init(Cipher.DECRYPT_MODE, key, params, random); return deccipher; } catch (GeneralSecurityException e) { // should not happen - throw new IllegalStateException("Could not instanciate decryptor", e); + throw new IllegalStateException("Could not instantiate decryptor", e); } } } diff --git a/core/camel-support/src/main/java/org/apache/camel/converter/stream/FileInputStreamCache.java b/core/camel-support/src/main/java/org/apache/camel/converter/stream/FileInputStreamCache.java index f6fe5d49789e9..370c1c8c48c52 100644 --- a/core/camel-support/src/main/java/org/apache/camel/converter/stream/FileInputStreamCache.java +++ b/core/camel-support/src/main/java/org/apache/camel/converter/stream/FileInputStreamCache.java @@ -65,12 +65,12 @@ public FileInputStreamCache(File file) { this(new TempFileManager(file, true)); } - FileInputStreamCache(TempFileManager closer) { - this.file = closer.getTempFile(); + FileInputStreamCache(TempFileManager tempFileManager) { + this.file = tempFileManager.getTempFile(); this.stream = null; - this.ciphers = closer.getCiphers(); + this.ciphers = tempFileManager.getCiphers(); this.length = file.length(); - this.tempFileManager = closer; + this.tempFileManager = tempFileManager; this.tempFileManager.add(this); } @@ -178,17 +178,7 @@ private InputStream getInputStream() throws IOException { private InputStream createInputStream(File file) throws IOException { InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath(), StandardOpenOption.READ)); if (ciphers != null) { - in = new CipherInputStream(in, ciphers.createDecryptor()) { - boolean closed; - - @Override - public void close() throws IOException { - if (!closed) { - super.close(); - closed = true; - } - } - }; + in = new CipherInputStream(in, ciphers.createDecryptor()); } return in; } @@ -314,22 +304,12 @@ OutputStream createOutputStream(StreamCachingStrategy strategy) throws IOExcepti if (ObjectHelper.isNotEmpty(strategy.getSpoolCipher())) { try { if (ciphers == null) { - ciphers = new CipherPair(strategy.getSpoolCipher()); + ciphers = new CipherPair(strategy.getSpoolCipher(), strategy.getSecureRandomParameters()); } } catch (GeneralSecurityException e) { throw new IOException(e.getMessage(), e); } - out = new CipherOutputStream(out, ciphers.getEncryptor()) { - boolean closed; - - @Override - public void close() throws IOException { - if (!closed) { - super.close(); - closed = true; - } - } - }; + out = new CipherOutputStream(out, ciphers.createEncryptor()); } outputStream = out; return out;