diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java index dafd371..e2c0387 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java @@ -17,11 +17,15 @@ package org.apache.logging.log4j.core.appender; import java.io.Serializable; +import java.security.GeneralSecurityException; import java.util.HashMap; import java.util.Map; +import javax.crypto.Cipher; + import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.appender.crypto.AbstractCipherConfiguration; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; @@ -112,6 +116,7 @@ @PluginElement("Filter") final Filter filter, @PluginAttribute("advertise") final String advertise, @PluginAttribute("advertiseUri") final String advertiseUri, + @PluginElement("Cipher") AbstractCipherConfiguration cipherConfiguration, @PluginConfiguration final Configuration config) { // @formatter:on final boolean isAppend = Booleans.parseBoolean(append, true); @@ -137,15 +142,26 @@ } if (fileName == null) { - LOGGER.error("No filename provided for FileAppender with name " + name); + LOGGER.error("No filename provided for FileAppender with name " + name); return null; } if (layout == null) { layout = PatternLayout.createDefaultLayout(); } - + + Cipher cipher = null; + int cipherBlockSize = -1; + if (cipherConfiguration != null) { + try { + cipher = cipherConfiguration.newCipher(); + cipherBlockSize = cipherConfiguration.getBlockSize(cipher); + } catch (GeneralSecurityException e) { + throw new IllegalArgumentException(e); + } + } + final FileManager manager = FileManager.getFileManager(fileName, isAppend, isLocking, isBuffered, advertiseUri, - layout, bufferSize); + layout, bufferSize, cipher, cipherBlockSize); if (manager == null) { return null; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java index b1843f9..f226c26 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java @@ -28,7 +28,11 @@ import java.util.HashMap; import java.util.Map; +import javax.crypto.Cipher; + import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.appender.crypto.CheckedCipherOutputStream; +import org.apache.logging.log4j.core.appender.crypto.ExactBlockSizeOutputStream; /** @@ -61,42 +65,56 @@ * @param advertiseUri the URI to use when advertising the file * @param layout The layout * @param bufferSize buffer size for buffered IO + * @param cipher A cipher or null. + * @param cipherBlockSize The cipher's block size of -1 if not used. * @return A FileManager for the File. */ public static FileManager getFileManager(final String fileName, final boolean append, boolean locking, final boolean bufferedIo, final String advertiseUri, final Layout layout, - final int bufferSize) { + final int bufferSize, Cipher cipher, int cipherBlockSize) { if (locking && bufferedIo) { locking = false; } return (FileManager) getManager(fileName, new FactoryData(append, locking, bufferedIo, bufferSize, - advertiseUri, layout), FACTORY); + advertiseUri, layout, cipher, cipherBlockSize), FACTORY); } @Override - protected synchronized void write(final byte[] bytes, final int offset, final int length) { - + protected synchronized void write(final byte[] bytes, final int offset, final int length) { + FileChannel channel = null; if (isLocking) { - final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel(); - try { - /* Lock the whole file. This could be optimized to only lock from the current file - position. Note that locking may be advisory on some systems and mandatory on others, - so locking just from the current position would allow reading on systems where - locking is mandatory. Also, Java 6 will throw an exception if the region of the - file is already locked by another FileChannel in the same JVM. Hopefully, that will - be avoided since every file should have a single file manager - unless two different - files strings are configured that somehow map to the same file.*/ - final FileLock lock = channel.lock(0, Long.MAX_VALUE, false); - try { - super.write(bytes, offset, length); - } finally { - lock.release(); - } - } catch (final IOException ex) { - throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex); + OutputStream outputStream = getOutputStream(); + if (outputStream instanceof ExactBlockSizeOutputStream) { + outputStream = ((ExactBlockSizeOutputStream) outputStream).getWrappedOutputStream(); } - + if (outputStream instanceof FileOutputStream) { + channel = ((FileOutputStream) outputStream).getChannel(); + } + if (channel != null) { + try { + /* + * Lock the whole file. This could be optimized to only lock from the current file position. Note + * that locking may be advisory on some systems and mandatory on others, so locking just from the + * current position would allow reading on systems where locking is mandatory. Also, Java 6 will + * throw an exception if the region of the file is already locked by another FileChannel in the same + * JVM. Hopefully, that will be avoided since every file should have a single file manager - unless + * two different files strings are configured that somehow map to the same file. + */ + final FileLock lock = channel.lock(0, Long.MAX_VALUE, false); + try { + super.write(bytes, offset, length); + } finally { + // TODO: Replace with close() in Java 7 to avoid resource leak warning. + lock.release(); + } + } catch (final IOException ex) { + throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex); + } + } + if (channel == null) { + super.write(bytes, offset, length); + } } else { super.write(bytes, offset, length); } @@ -157,6 +175,8 @@ private final int bufferSize; private final String advertiseURI; private final Layout layout; + private final Cipher cipher; + private final int cipherBlockSize; /** * Constructor. @@ -165,15 +185,19 @@ * @param bufferedIO Buffering flag. * @param bufferSize Buffer size. * @param advertiseURI the URI to use when advertising the file + * @param cipher A cipher or null. + * @param cipherBlockSize The cipher's block size of -1 if not used. */ public FactoryData(final boolean append, final boolean locking, final boolean bufferedIO, final int bufferSize, - final String advertiseURI, final Layout layout) { + final String advertiseURI, final Layout layout, Cipher cipher, int cipherBlockSize) { this.append = append; this.locking = locking; this.bufferedIO = bufferedIO; this.bufferSize = bufferSize; this.advertiseURI = advertiseURI; this.layout = layout; + this.cipher = cipher; + this.cipherBlockSize = cipherBlockSize; } } @@ -205,9 +229,13 @@ } else { bufferSize = -1; // signals to RollingFileManager not to use BufferedOutputStream } + if (data.cipher != null) { + os = new ExactBlockSizeOutputStream(new CheckedCipherOutputStream(os, data.cipher), data.cipherBlockSize, + (byte) ' '); + } return new FileManager(name, os, data.append, data.locking, data.advertiseURI, data.layout, bufferSize); - } catch (final FileNotFoundException ex) { - LOGGER.error("FileManager (" + name + ") " + ex); + } catch (final FileNotFoundException e) { + LOGGER.error("FileManager (" + name + ") " + e, e); } return null; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/AbstractCipherConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/AbstractCipherConfiguration.java new file mode 100644 index 0000000..892ee06 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/AbstractCipherConfiguration.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.interfaces.RSAKey; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +@Plugin(name = "Cipher", category = "Core", printObject = true) +public abstract class AbstractCipherConfiguration { + + public static enum Mode { + + DECRYPT(Cipher.DECRYPT_MODE), ENCRYPT(Cipher.ENCRYPT_MODE); + + private final int mode; + + Mode(final int mode) { + this.mode = mode; + } + + public int getIntMode() { + return this.mode; + } + } + + /** + * Creates a CipherConfiguration + * + * @param transformation + * @param provider + * @param mode + * @param certificateConfiguration + * @param keyConfiguration + * @return + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws InvalidAlgorithmParameterException + */ + @PluginFactory + public static AbstractCipherConfiguration create( // @formatter:off + @PluginAttribute("transformation") final String transformation, + @PluginAttribute("provider") final String provider, + @PluginAttribute(value="mode") final Mode mode, + @PluginElement("Certificate") final CertificateConfiguration certificateConfiguration, + @PluginElement("Key") final BasicKeyConfiguration keyConfiguration) + // @formatter:on + throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, + InvalidAlgorithmParameterException { + if (certificateConfiguration != null && keyConfiguration != null) { + throw new IllegalArgumentException("Only one of Certificate or Key must be set"); + } + if (certificateConfiguration != null) { + return new CipherCertificateConfiguration(transformation, provider, mode, certificateConfiguration); + } else if (keyConfiguration != null) { + return new CipherKeyConfiguration(transformation, provider, mode, keyConfiguration); + } + throw new IllegalArgumentException("Either Certificate or Key must be set."); + } + + protected final Mode mode; + protected final String provider; + + protected final String transformation; + + public AbstractCipherConfiguration(final String transformation, final String provider, final Mode mode) { + this.transformation = transformation; + this.provider = provider; + this.mode = mode; + } + + public abstract int getBlockSize(final Cipher cipher); + + /** + * @param key + * @return + */ + protected int getBlockSize(final Key key) { + if (key instanceof RSAKey) { + final int keySize = ((RSAKey) key).getModulus().bitLength(); + final int rsaOverhead = 11; + return (keySize / 8) - rsaOverhead; + } + // some default + return 8192; + } + + public Mode getMode() { + return this.mode; + } + + public String getProvider() { + return this.provider; + } + + public String getTransformation() { + return this.transformation; + } + + public abstract Cipher newCipher() throws GeneralSecurityException; + +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/BasicKeyConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/BasicKeyConfiguration.java new file mode 100644 index 0000000..c63a85c --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/BasicKeyConfiguration.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; + +public class BasicKeyConfiguration { + + public static BasicKeyConfiguration create(final T key) { + return new BasicKeyConfiguration(key); + } + + private final P algorithmParameterSpec; + + private final K key; + + public BasicKeyConfiguration(final K key) { + this(key, null); + } + + protected BasicKeyConfiguration(final K key, final P algorithmParameterSpec) { + super(); + this.key = key; + this.algorithmParameterSpec = algorithmParameterSpec; + } + + public P getAlgorithmParameterSpec() { + return this.algorithmParameterSpec; + } + + public K getKey() { + return this.key; + } +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CertificateConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CertificateConfiguration.java new file mode 100644 index 0000000..7a23c7c --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CertificateConfiguration.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +@Plugin(name = "Certificate", category = "Core", printObject = true) +public class CertificateConfiguration { + + private static final String X_509 = "X.509"; + + @PluginFactory + public static CertificateConfiguration create( + // @formatter:off + @PluginAttribute("file") final File file, + @PluginAttribute(value="type", defaultString=X_509) final String type) + // @formatter:on + throws CertificateException, IOException { + final FileInputStream fis = new FileInputStream(file); + try { + final CertificateFactory cf = CertificateFactory.getInstance(type); + return new CertificateConfiguration(cf.generateCertificate(fis)); + } finally { + fis.close(); + } + } + + private final Certificate certificate; + + public CertificateConfiguration(final Certificate certificate) { + this.certificate = certificate; + } + + public Certificate getCertificate() { + return this.certificate; + } + +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CheckedCipherOutputStream.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CheckedCipherOutputStream.java new file mode 100644 index 0000000..436b891 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CheckedCipherOutputStream.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NullCipher; + +/** + * Like a {@CipherOutputStream}, but this one does not catch and ignore exceptions, which + * is better for troubleshooting. + */ +public final class CheckedCipherOutputStream extends FilterOutputStream { + + private byte[] buffer; + + private final byte[] buffer1 = new byte[1]; + + private final Cipher cipher; + + private int lastUpdateLen; + + private final OutputStream outputStream; + + private int totalUpdate; + + protected CheckedCipherOutputStream(final OutputStream outputStream) { + super(outputStream); + this.outputStream = outputStream; + this.cipher = new NullCipher(); + } + + public CheckedCipherOutputStream(final OutputStream outputStream, final Cipher cipher) { + super(outputStream); + this.outputStream = outputStream; + this.cipher = cipher; + } + + @Override + public void close() throws IOException { + try { + this.buffer = this.cipher.doFinal(); + this.flush(); + } catch (final IllegalBlockSizeException e) { + throw new IOException(CheckedCipherOutputStream.class.getName() + ".close(): totalUpdate = " + + this.totalUpdate + ", lastUpdate = " + this.lastUpdateLen + ", caused by: " + e, e); + } catch (final BadPaddingException e) { + throw new IOException(CheckedCipherOutputStream.class.getName() + ".close(): totalUpdate = " + + this.totalUpdate + ", lastUpdate = " + this.lastUpdateLen + ", caused by: " + e, e); + } finally { + this.out.close(); + } + } + + @Override + public void flush() throws IOException { + if (this.buffer != null) { + this.outputStream.write(this.buffer); + this.buffer = null; + } + this.outputStream.flush(); + } + + @Override + public void write(final byte b[]) throws IOException { + this.write(b, 0, b.length); + } + + @Override + public void write(final byte b[], final int off, final int len) throws IOException { + this.buffer = this.cipher.update(b, off, len); + this.lastUpdateLen = len; + this.totalUpdate += len; + if (this.buffer != null) { + this.outputStream.write(this.buffer); + this.buffer = null; + } + } + + @Override + public void write(final int b) throws IOException { + this.buffer1[0] = (byte) b; + this.buffer = this.cipher.update(this.buffer1, 0, 1); + this.lastUpdateLen = 1; + this.totalUpdate++; + if (this.buffer != null) { + this.outputStream.write(this.buffer); + this.buffer = null; + } + } + +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CipherCertificateConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CipherCertificateConfiguration.java new file mode 100644 index 0000000..8a6fc8c --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CipherCertificateConfiguration.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; + +public class CipherCertificateConfiguration extends AbstractCipherConfiguration { + + protected final CertificateConfiguration certificateConfiguration; + + public CipherCertificateConfiguration(final String transformation, final String provider, final Mode mode, + final CertificateConfiguration certificateConfiguration) { + super(transformation, provider, mode); + this.certificateConfiguration = certificateConfiguration; + } + + @Override + public int getBlockSize(final Cipher cipher) { + final int blockSize = cipher.getBlockSize(); + return blockSize > 0 ? blockSize : this.getBlockSize(this.certificateConfiguration.getCertificate() + .getPublicKey()); + } + + @Override + public Cipher newCipher() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, + InvalidKeyException { + final Cipher cipher = this.provider == null ? Cipher.getInstance(this.transformation) : Cipher.getInstance( + this.transformation, this.provider); + final Mode safeMode = this.mode == null ? Mode.ENCRYPT : this.mode; + cipher.init(safeMode.getIntMode(), this.certificateConfiguration.getCertificate()); + return cipher; + } + + @Override + public String toString() { + return "CipherCertificateConfiguration [certificateConfiguration=" + this.certificateConfiguration + + ", transformation=" + this.transformation + ", provider=" + this.provider + ", mode=" + this.mode + + "]"; + } + +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CipherKeyConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CipherKeyConfiguration.java new file mode 100644 index 0000000..5ebb85e --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/CipherKeyConfiguration.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; + +public class CipherKeyConfiguration extends AbstractCipherConfiguration { + + protected final BasicKeyConfiguration keyConfiguration; + + public CipherKeyConfiguration(final String transformation, final String provider, final Mode mode, + final BasicKeyConfiguration keyConfiguration) { + super(transformation, provider, mode); + this.keyConfiguration = keyConfiguration; + } + + @Override + public int getBlockSize(final Cipher cipher) { + final Key key = this.keyConfiguration.getKey(); + final int blockSize = cipher.getBlockSize(); + if (blockSize > 0) { + return blockSize; + } + return this.getBlockSize(key); + } + + @Override + public Cipher newCipher() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, + InvalidKeyException, InvalidAlgorithmParameterException { + final Cipher cipher = this.provider == null ? Cipher.getInstance(this.transformation) : Cipher.getInstance( + this.transformation, this.provider); + final Mode safeMode = this.mode == null ? Mode.ENCRYPT : this.mode; + cipher.init(safeMode.getIntMode(), this.keyConfiguration.getKey(), + this.keyConfiguration.getAlgorithmParameterSpec()); + return cipher; + } + + @Override + public String toString() { + return "CipherKeyConfiguration [keyConfiguration=" + this.keyConfiguration + ", transformation=" + + this.transformation + ", provider=" + this.provider + ", mode=" + this.mode + "]"; + } + +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/ExactBlockSizeOutputStream.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/ExactBlockSizeOutputStream.java new file mode 100644 index 0000000..a080710 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/ExactBlockSizeOutputStream.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.io.BufferedOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * Like a {@link BufferedOutputStream} but writes in blocks of the given size only. + */ +public final class ExactBlockSizeOutputStream extends FilterOutputStream { + + private final byte buffer[]; + + private int count; + + private final byte eosPadByte; + + public ExactBlockSizeOutputStream(final OutputStream outputStream, final int blockSize, final byte eosPadByte) { + super(outputStream); + this.eosPadByte = eosPadByte; + if (blockSize <= 0) { + throw new IllegalArgumentException("blockSize <= 0"); + } + this.buffer = new byte[blockSize]; + } + + @Override + public void close() throws IOException { + try { + if (this.count > 0) { + // pad EOS + Arrays.fill(this.buffer, this.count, this.buffer.length, this.eosPadByte); + this.count = this.buffer.length; + } + this.flush(); + } finally { + try { + this.out.close(); + } catch (final IOException e) { + throw new IOException(ExactBlockSizeOutputStream.class.getName() + ".close(): buffer.length=" + + this.buffer.length + ", count=" + count + ", caused by: " + e, e); + } + } + } + + @Override + public synchronized void flush() throws IOException { + this.flushInternalBuffer(); + this.out.flush(); + } + + private void flushInternalBuffer() throws IOException { + if (this.count > 0) { + this.out.write(this.buffer, 0, this.count); + this.count = 0; + } + } + + public OutputStream getWrappedOutputStream() { + return this.out; + } + + @Override + public synchronized void write(final byte in[], final int offset, final int length) throws IOException { + if (this.count + length <= this.buffer.length) { + // we will not fill up the buffer: copy and done. + System.arraycopy(in, offset, this.buffer, this.count, length); + this.count += length; + return; + } + int writeSize = length; + int srcPos = offset; + while (writeSize > 0) { + if (this.count == this.buffer.length) { + this.flushInternalBuffer(); + } + final int blockSize = Math.min(writeSize, this.buffer.length - this.count); + System.arraycopy(in, srcPos, this.buffer, this.count, blockSize); + this.count += blockSize; + srcPos += blockSize; + writeSize -= blockSize; + } + } + + @Override + public synchronized void write(final int b) throws IOException { + if (this.count >= this.buffer.length) { + this.flushInternalBuffer(); + } + this.buffer[this.count++] = (byte) b; + } +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/KeyConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/KeyConfiguration.java new file mode 100644 index 0000000..17b5ace --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/KeyConfiguration.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.spec.AlgorithmParameterSpec; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +@Plugin(name = "Key", category = "Core", printObject = true) +public class KeyConfiguration extends BasicKeyConfiguration { + + @PluginFactory + public static KeyConfiguration create( + // @formatter:off + @PluginElement("KeyStore") final KeyStore keyStore, @PluginAttribute("alias") final String alias, + @PluginAttribute("password") final char[] password) + // @formatter:on + throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { + return new KeyConfiguration(keyStore.getKey(alias, password)); + } + + protected KeyConfiguration(final Key privateKey) { + super(privateKey); + } + +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/KeyStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/KeyStoreConfiguration.java new file mode 100644 index 0000000..254bd94 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/KeyStoreConfiguration.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.CertificateException; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +@Plugin(name = "KeyStore", category = "Core", printObject = true) +public class KeyStoreConfiguration { + + @PluginFactory + public static KeyStore createKeyStore( + // @formatter:off + @PluginAttribute("file") final File file, + @PluginAttribute("type") final String type, + @PluginAttribute("provider") final String provider, + @PluginAttribute("password") final char[] password) + // @formatter:on + throws NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException, CertificateException, + IOException { + final KeyStore keyStore = provider == null ? KeyStore.getInstance(type) : KeyStore.getInstance(type, provider); + final FileInputStream stream = new FileInputStream(file); + try { + keyStore.load(stream, password); + } finally { + stream.close(); + } + return keyStore; + } + +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/PKCS8PrivateKeyConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/PKCS8PrivateKeyConfiguration.java new file mode 100644 index 0000000..b4a3cfd --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/PKCS8PrivateKeyConfiguration.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +@Plugin(name = "Pkcs8PrivateKey", category = "Core", printObject = true) +public class PKCS8PrivateKeyConfiguration extends BasicKeyConfiguration { + + private static final String BEGIN = "-----BEGIN"; + private static final String END = "-----END"; + + @PluginFactory + public static PKCS8PrivateKeyConfiguration create( + // @formatter:off + @PluginAttribute("file") final File file, + @PluginAttribute("algorithm") final String algorithm) + // @formatter:on + throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + final RandomAccessFile raf = new RandomAccessFile(file, "r"); + byte[] buf; + try { + buf = new byte[(int) raf.length()]; + raf.readFully(buf); + } finally { + raf.close(); + } + final String s = new String(buf); + if (s.startsWith(BEGIN)) { + final int r = s.indexOf('\r'); + final int n = s.indexOf('\n'); + final int start = Math.max(r, n); + if (start < 0) { + throw new IllegalArgumentException("No end of line after " + BEGIN + file); + } + final int end = s.indexOf(END); + if (end < 0) { + throw new IllegalArgumentException("No " + END + " marker" + file); + } + final String b64 = s.substring(start, end); + buf = org.apache.commons.codec.binary.Base64.decodeBase64(b64); + } + final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buf); + final KeyFactory keyFactory = KeyFactory.getInstance(algorithm); + final PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + return new PKCS8PrivateKeyConfiguration(privateKey, null); + } + + protected PKCS8PrivateKeyConfiguration(final PrivateKey privateKey, + final AlgorithmParameterSpec AlgorithmParameterSpec) { + super(privateKey, AlgorithmParameterSpec); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/PbeKeyConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/PbeKeyConfiguration.java new file mode 100644 index 0000000..f06c8d4 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/PbeKeyConfiguration.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +@Plugin(name = "PbeKey", category = "Core", printObject = true) +public class PbeKeyConfiguration extends BasicKeyConfiguration { + + /** + * Creates a PbeKeyConfiguration with a SecretKey and a PBEParameterSpec. + * + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + */ + @PluginFactory + public static PbeKeyConfiguration create( + // @formatter:off + @PluginAttribute("salt") final byte[] salt, + @PluginAttribute("count") final int count, + @PluginAttribute("password") final char[] password, + @PluginAttribute("algorithm") final String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException { + // @formatter:on + final PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count); + final PBEKeySpec pbeKeySpec = new PBEKeySpec(password); + final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm); + final SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec); + return new PbeKeyConfiguration(pbeKey, pbeParamSpec); + } + + protected PbeKeyConfiguration(final SecretKey secretKey, final PBEParameterSpec pbeParameterSpec) { + super(secretKey, pbeParameterSpec); + } + +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/SecretKeyConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/SecretKeyConfiguration.java new file mode 100644 index 0000000..70936c8 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/crypto/SecretKeyConfiguration.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.appender.crypto; + +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +@Plugin(name = "SecretKey", category = "Core", printObject = true) +public class SecretKeyConfiguration extends BasicKeyConfiguration { + + /** + * Creates a SecretKeyConfiguration with a SecretKeySpec and IvParameterSpec. + * + * @param key + * @param algorithm + * @param initializationVector + * @param iv + * @return + */ + @PluginFactory + public static SecretKeyConfiguration create( // @formatter:off + @PluginAttribute("key") final byte[] key, + @PluginAttribute("algorithm") final String algorithm, + @PluginAttribute(value="iv") final byte[] initializationVector) { + // @formatter:on + final IvParameterSpec ivParameterSpec = initializationVector == null ? null : new IvParameterSpec( + initializationVector); + return new SecretKeyConfiguration(new SecretKeySpec(key, algorithm), ivParameterSpec); + } + + protected SecretKeyConfiguration(final SecretKeySpec secretKeySpec, final IvParameterSpec ivParameterSpec) { + super(secretKeySpec, ivParameterSpec); + } + +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/ByteArrayParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/ByteArrayParser.java new file mode 100644 index 0000000..84b8e11 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/ByteArrayParser.java @@ -0,0 +1,39 @@ +package org.apache.logging.log4j.core.config.plugins.util; + +import java.nio.charset.Charset; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; + +/** + * Depends on Apache Commons Codec, so leave it in a separate class. + * + * Future: Java 8 includes a Base64 class. + */ +class ByteArrayParser { + + private static final String PREFIX_0X = "0x"; + private static final String PREFIX_BASE64 = "Base64:"; + + static byte[] parse(String value) { + byte[] bytes; + if (value == null || value.isEmpty()) { + bytes = new byte[0]; + } else if (value.startsWith(PREFIX_BASE64)) { + String base64 = value.substring(PREFIX_BASE64.length()); + bytes = new Base64().decode(base64); + } else if (value.startsWith(PREFIX_0X)) { + String hex = value.substring(PREFIX_0X.length()); + try { + bytes = Hex.decodeHex(hex.toCharArray()); + } catch (DecoderException e) { + // For now, we convert to an IAE to keep the Commons Codec hard reference in this class. + throw new IllegalArgumentException(value, e); + } + } else { + bytes = value.getBytes(Charset.defaultCharset()); + } + return bytes; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java index 26d57e2..9fb861a 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java @@ -16,96 +16,488 @@ */ package org.apache.logging.log4j.core.appender; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.Collection; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.IllegalBlockSizeException; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.crypto.AbstractCipherConfiguration; +import org.apache.logging.log4j.core.appender.crypto.BasicKeyConfiguration; +import org.apache.logging.log4j.core.appender.crypto.CheckedCipherOutputStream; +import org.apache.logging.log4j.core.appender.crypto.ExactBlockSizeOutputStream; +import org.apache.logging.log4j.core.appender.crypto.PbeKeyConfiguration; +import org.apache.logging.log4j.core.appender.crypto.SecretKeyConfiguration; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.junit.CleanFiles; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.util.Strings; +import org.junit.After; import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; - -import static org.junit.Assert.*; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; /** - * + * Tests the file appender with and without encryption. */ +@RunWith(Parameterized.class) public class FileAppenderTest { private static final String FILENAME = "target/fileAppenderTest.log"; private static final int THREADS = 2; + private static final String FILENAME_DECRYPTED = "target/fileAppenderTest.txt"; @Rule - public CleanFiles files = new CleanFiles(FILENAME); + public CleanFiles files = new CleanFiles(FILENAME, FILENAME_DECRYPTED); + + private final AbstractCipherConfiguration cipherConfigurationEncrypt; + private final AbstractCipherConfiguration cipherConfigurationDecrypt; + + private Cipher cipherEncrypt; + private Cipher cipherDecrypt; + + private static final String AES = "AES"; + private static final String DES = "DES"; + private static final String DESede = "DESede"; + private static final String RSA = "RSA"; + + /** + * You should not use RSA to encrypt a log, but we have some test code we still want to compile and be available for + * refactoring. Some tests fail under RSA but I will not bother with those since using RSA for larger data sets is a + * bad idea. ggregory-2014-05-29. + */ + private static final boolean FORCE_RSA_TESTS = false; + + /** + * According to the {@link Cipher} Javadoc, every implementation of the Java platform is required to support the + * following standard cipher transformations with the key sizes in parentheses: + *
    + *
  • AES/CBC/NoPadding (128)
  • + *
  • AES/CBC/PKCS5Padding (128)
  • + *
  • AES/ECB/NoPadding (128)
  • + *
  • AES/ECB/PKCS5Padding (128)
  • + *
  • DES/CBC/NoPadding (56)
  • + *
  • DES/CBC/PKCS5Padding (56)
  • + *
  • DES/ECB/NoPadding (56)
  • + *
  • DES/ECB/PKCS5Padding (56)
  • + *
  • DESede/CBC/NoPadding (168)
  • + *
  • DESede/CBC/PKCS5Padding (168)
  • + *
  • DESede/ECB/NoPadding (168)
  • + *
  • DESede/ECB/PKCS5Padding (168)
  • + *
  • RSA/ECB/PKCS1Padding (1024, 2048)
  • + *
  • RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
  • + *
  • RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
  • + *
+ */ + @Parameters + public static Collection data() throws Exception { + final byte[] salt = { + (byte) 0xc7, + (byte) 0x73, + (byte) 0x21, + (byte) 0x8c, + (byte) 0x7e, + (byte) 0xc8, + (byte) 0xee, + (byte) 0x99 }; + final byte[] key128 = new byte[16]; + final byte[] key56 = new byte[8]; // Yes, 8, not, 7, due to parity bits. + final byte[] key168 = new byte[24]; // Yes, 24 and not 21. + final byte[] iv128 = new byte[16]; + final byte[] iv64 = new byte[8]; + final KeyPairGenerator keyPairGeneratorRsa1024 = KeyPairGenerator.getInstance(RSA); + final KeyPairGenerator keyPairGeneratorRsa2048 = KeyPairGenerator.getInstance(RSA); + keyPairGeneratorRsa1024.initialize(1024); + keyPairGeneratorRsa2048.initialize(2048); + final KeyPair keyPairRsa1024 = keyPairGeneratorRsa1024.generateKeyPair(); + final KeyPair keyPairRsa2048 = keyPairGeneratorRsa2048.generateKeyPair(); + final PublicKey newPublicKeyRsa1024 = keyPairRsa1024.getPublic(); + final PublicKey newPublicKeyRsa2048 = keyPairRsa1024.getPublic(); + final PrivateKey newPrivateKeyRsa1024 = keyPairRsa2048.getPrivate(); + final PrivateKey newPrivateKeyRsa2048 = keyPairRsa2048.getPrivate(); + return Arrays.asList(new Object[][] { + // 0 + // No encryption + { + null, + null }, + // + // AES + // + // TODO: Need a better way to specify the key... + // 1 + { + AbstractCipherConfiguration.create("AES/CBC/NoPadding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key128, AES, iv128)), + AbstractCipherConfiguration.create("AES/CBC/NoPadding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key128, AES, iv128)) }, + // 2 + { + AbstractCipherConfiguration.create("AES/CBC/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key128, AES, iv128)), + AbstractCipherConfiguration.create("AES/CBC/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key128, AES, iv128)) }, + // 3 + { + AbstractCipherConfiguration.create("AES/ECB/NoPadding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key128, AES, null)), + AbstractCipherConfiguration.create("AES/ECB/NoPadding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key128, AES, null)) }, + // 4 + { + AbstractCipherConfiguration.create("AES/ECB/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key128, AES, null)), + AbstractCipherConfiguration.create("AES/ECB/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key128, AES, null)) }, + // + // DES + // Some algorithms MUST use an IV, ECB MUST not. + // + // TODO: Need a better way to specify the key... + // 5 + { + AbstractCipherConfiguration.create("DES/CBC/NoPadding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key56, DES, iv64)), + AbstractCipherConfiguration.create("DES/CBC/NoPadding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key56, DES, iv64)) }, + // 6 + { + AbstractCipherConfiguration.create("DES/CBC/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key56, DES, iv64)), + AbstractCipherConfiguration.create("DES/CBC/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key56, DES, iv64)) }, + // 7 + { + AbstractCipherConfiguration.create("DES/ECB/NoPadding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key56, DES, null)), + AbstractCipherConfiguration.create("DES/ECB/NoPadding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key56, DES, null)) }, + // 8 + { + AbstractCipherConfiguration.create("DES/ECB/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key56, DES, null)), + AbstractCipherConfiguration.create("DES/ECB/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key56, DES, null)) }, + // + // DESede + // Some algorithms MUST use an IV, ECB MUST not. + // + // TODO: Need a better way to specify the key... + // 9 + { + AbstractCipherConfiguration.create("DESede/CBC/NoPadding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key168, DESede, iv64)), + AbstractCipherConfiguration.create("DESede/CBC/NoPadding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key168, DESede, iv64)) }, + // 10 + { + AbstractCipherConfiguration.create("DESede/CBC/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key168, DESede, iv64)), + AbstractCipherConfiguration.create("DESede/CBC/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key168, DESede, iv64)) }, + // 11 + { + AbstractCipherConfiguration.create("DESede/ECB/NoPadding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key168, DESede, null)), + AbstractCipherConfiguration.create("DESede/ECB/NoPadding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key168, DESede, null)) }, + // 12 + { + AbstractCipherConfiguration.create("DESede/ECB/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + SecretKeyConfiguration.create(key168, DESede, null)), + AbstractCipherConfiguration.create("DESede/ECB/PKCS5Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + SecretKeyConfiguration.create(key168, DESede, null)) }, + // + // RSA 1024 + // Don't use RSA for logs, but this helps debug. + // + // TODO: Need a better way to specify the key... + // 13 + { + AbstractCipherConfiguration.create("RSA/ECB/PKCS1Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + BasicKeyConfiguration.create(newPublicKeyRsa1024)), + AbstractCipherConfiguration.create("RSA/ECB/PKCS1Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + BasicKeyConfiguration.create(newPrivateKeyRsa1024)) }, + // 14 + { + AbstractCipherConfiguration.create("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + BasicKeyConfiguration.create(newPublicKeyRsa1024)), + AbstractCipherConfiguration.create("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + BasicKeyConfiguration.create(newPrivateKeyRsa1024)) }, + // 15 + { + AbstractCipherConfiguration.create("RSA/ECB/OAEPWithSHA-256AndMGF1Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + BasicKeyConfiguration.create(newPublicKeyRsa1024)), + AbstractCipherConfiguration.create("RSA/ECB/OAEPWithSHA-256AndMGF1Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + BasicKeyConfiguration.create(newPrivateKeyRsa1024)) }, + // + // RSA 2048 + // Don't use RSA for logs, but this helps debug. + // + // TODO: Need a better way to specify the key... + // 16 + { + AbstractCipherConfiguration.create("RSA/ECB/PKCS1Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + BasicKeyConfiguration.create(newPublicKeyRsa2048)), + AbstractCipherConfiguration.create("RSA/ECB/PKCS1Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + BasicKeyConfiguration.create(newPrivateKeyRsa2048)) }, + // 17 + { + AbstractCipherConfiguration.create("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + BasicKeyConfiguration.create(newPublicKeyRsa2048)), + AbstractCipherConfiguration.create("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + BasicKeyConfiguration.create(newPrivateKeyRsa2048)) }, + // 18 + { + AbstractCipherConfiguration.create("RSA/ECB/OAEPWithSHA-256AndMGF1Padding", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + BasicKeyConfiguration.create(newPublicKeyRsa2048)), + AbstractCipherConfiguration.create("RSA/ECB/OAEPWithSHA-256AndMGF1Padding", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + BasicKeyConfiguration.create(newPrivateKeyRsa2048)) }, + // Symmetric-key encryption: password-based encryption (PBE) -- not required to be in the JRE. + // 19 + { + AbstractCipherConfiguration.create("PBEWithMD5AndDES", null, + AbstractCipherConfiguration.Mode.ENCRYPT, null, + PbeKeyConfiguration.create(salt, 20, "changeit".toCharArray(), "PBEWithMD5AndDES")), + AbstractCipherConfiguration.create("PBEWithMD5AndDES", null, + AbstractCipherConfiguration.Mode.DECRYPT, null, + PbeKeyConfiguration.create(salt, 20, "changeit".toCharArray(), "PBEWithMD5AndDES")) }, + // Certificate + // { CipherConfiguration.create(RSA, null, CipherConfiguration.Mode.ENCRYPT, + // CertificateConfiguration.create(new File("src/test/resources/crypto/sample-cert.txt"), "X.509"), + // null), + // CipherConfiguration.create(RSA, null, CipherConfiguration.Mode.DECRYPT, null, + // PKCS8PrivateKeyConfiguration.create(new File("src/test/resources/crypto/private-key-pkcs8.txt"), + // RSA))}, + // + // One should not use RSA as a log cipher, but we do this for checking the streaming guts for using the + // correct block size. + // { + // AbstractCipherConfiguration.create(RSA, null, AbstractCipherConfiguration.Mode.ENCRYPT, + // CertificateConfiguration.create(new File("src/test/resources/crypto/sample-cert.txt"), + // "X.509"), null), + // AbstractCipherConfiguration.create(RSA, null, AbstractCipherConfiguration.Mode.DECRYPT, null, + // KeyConfiguration.create(KeyStoreConfiguration.createKeyStore(new File( + // "src/test/resources/crypto/test.jks"), "JKS", null, "log4j".toCharArray()), + // "log4jsample", "log4j".toCharArray())) }, + // + }); + } + + public FileAppenderTest(final AbstractCipherConfiguration cipherConfigurationEncrypt, + final AbstractCipherConfiguration cipherConfigurationDecrypt) { + this.cipherConfigurationEncrypt = cipherConfigurationEncrypt; + this.cipherConfigurationDecrypt = cipherConfigurationDecrypt; + } @AfterClass public static void cleanupClass() { - assertTrue("Manager for " + FILENAME + " not removed", !OutputStreamManager.hasManager(FILENAME)); + assertTrue("Manager for " + FILENAME + " not removed", !AbstractManager.hasManager(FILENAME)); + assertTrue("Manager for " + FILENAME + " not removed", !AbstractManager.hasManager(FILENAME_DECRYPTED)); + } + + @Test + public void testAppenderSmallest() throws Exception { + assumeNotRsa(); + final int logEventCount = 1; + writer(false, logEventCount, "test", cipherConfigurationEncrypt); + this.verifyFile(logEventCount); + } + + @Before + public void setUp() throws GeneralSecurityException { + resetCiphers(); + } + + /** + * Don't bother testing with RSA because it is not meant for large data, only for keys. + */ + private void assumeNotRsa() { + if (this.cipherConfigurationEncrypt != null + && this.cipherConfigurationEncrypt.getTransformation().startsWith(RSA)) { + Assume.assumeTrue(FORCE_RSA_TESTS); + } + } + + @After + public void tearDown() throws GeneralSecurityException { + resetCiphers(); + } + + /** + * @throws GeneralSecurityException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + private void resetCiphers() throws GeneralSecurityException { + if (this.cipherConfigurationEncrypt != null) { + this.cipherEncrypt = this.cipherConfigurationEncrypt.newCipher(); + } + if (this.cipherConfigurationDecrypt != null) { + this.cipherDecrypt = this.cipherConfigurationDecrypt.newCipher(); + } + } + + /** + * Helps in debugging {@link AbstractCipherConfiguration}s before considering the additional complexities of Log4j. + * + * @throws Exception + */ + @Test + public void testCipherRoundtripSanityCheck() throws Exception { + assumeNotRsa(); + Assume.assumeNotNull(this.cipherEncrypt); + Assume.assumeNotNull(this.cipherDecrypt); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final int blockSize = this.cipherConfigurationEncrypt.getBlockSize(this.cipherEncrypt); + byte[] expectedBytes = "this is a message from the future... woo1 woo2 woo3 woo4.".getBytes(Charset + .defaultCharset()); + final OutputStream outputStream = new ExactBlockSizeOutputStream(new CheckedCipherOutputStream(baos, + this.cipherEncrypt), blockSize, (byte) ' '); + outputStream.write(expectedBytes); + outputStream.close(); + final byte[] cipherBytes = baos.toByteArray(); + final CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(cipherBytes), + this.cipherDecrypt); + byte actualBytes[] = new byte[expectedBytes.length]; + IOUtils.read(cipherInputStream, actualBytes); + cipherInputStream.close(); + assertArrayEquals(new String(actualBytes, Charset.defaultCharset()), expectedBytes, actualBytes); } @Test public void testAppender() throws Exception { - writer(false, 1, "test"); - verifyFile(1); + assumeNotRsa(); + final int logEventCount = 100; + writer(false, logEventCount, "test", cipherConfigurationEncrypt); + this.verifyFile(logEventCount); } @Test public void testSmallestBufferSize() throws Exception { - final Layout layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN).build(); + assumeNotRsa(); + final Layout layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN) + .build(); final String bufferSizeStr = "1"; final FileAppender appender = FileAppender.createAppender(FILENAME, "true", "false", "test", "false", "false", - "false", bufferSizeStr, layout, null, "false", null, null); - appender.start(); - final File file = new File(FILENAME); - assertTrue("Appender did not start", appender.isStarted()); - long curLen = file.length(); - long prevLen = curLen; - assertTrue("File length: " + curLen, curLen == 0); - for (int i = 0; i < 100; ++i) { - final LogEvent event = new Log4jLogEvent("TestLogger", null, FileAppenderTest.class.getName(), Level.INFO, - new SimpleMessage("Test"), null, null, null, this.getClass().getSimpleName(), null, - System.currentTimeMillis()); - try { - appender.append(event); - curLen = file.length(); - assertTrue("File length: " + curLen, curLen > prevLen); - Thread.sleep(25); // Give up control long enough for another thread/process to occasionally do - // something. - } catch (final Exception ex) { - throw ex; + "false", bufferSizeStr, layout, null, "false", null, this.cipherConfigurationEncrypt, null); + try { + appender.start(); + final File file = new File(FILENAME); + assertTrue("Appender did not start", appender.isStarted()); + long curLen = file.length(); + long prevLen = curLen; + assertTrue("File length: " + curLen + ", cipherConfiguration: " + this.cipherConfigurationEncrypt, + curLen == 0); + for (int i = 0; i < 100; ++i) { + final LogEvent event = new Log4jLogEvent("TestLogger", null, FileAppenderTest.class.getName(), + Level.INFO, new SimpleMessage("Test"), null, null, null, this.getClass().getSimpleName(), null, + System.currentTimeMillis()); + try { + appender.append(event); + curLen = file.length(); + final boolean condition = curLen > prevLen; + assertTrue("File length: " + curLen + ", cipherConfiguration=" + this.cipherConfigurationEncrypt, + (this.cipherConfigurationEncrypt == null && condition) + || this.cipherConfigurationEncrypt != null); + Thread.sleep(25); // Give up control long enough for another thread/process to occasionally do + // something. + } catch (final Exception ex) { + throw ex; + } + prevLen = curLen; } - prevLen = curLen; + } finally { + appender.stop(); } - appender.stop(); assertFalse("Appender did not stop", appender.isStarted()); } @Test public void testLockingAppender() throws Exception { - writer(true, 1, "test"); - verifyFile(1); + assumeNotRsa(); + writer(true, 1, "test", cipherConfigurationEncrypt); + this.verifyFile(1); } - @Test - public void testMultipleAppenders() throws Exception { + private void testMultipleAppenders(final boolean lock) throws Exception { + assumeNotRsa(); final ExecutorService pool = Executors.newFixedThreadPool(THREADS); final int count = 10; - final Runnable runnable = new FileWriterRunnable(false, count); + final Runnable runnable = new FileWriterRunnable(lock, count, cipherConfigurationEncrypt); for (int i = 0; i < THREADS; ++i) { pool.execute(runnable); } @@ -115,22 +507,18 @@ } @Test - public void testMultipleLockedAppenders() throws Exception { - final ExecutorService pool = Executors.newFixedThreadPool(THREADS); - final int count = 10; - final Runnable runnable = new FileWriterRunnable(true, count); - for (int i = 0; i < THREADS; ++i) { - pool.execute(runnable); - } - pool.shutdown(); - pool.awaitTermination(10, TimeUnit.SECONDS); - verifyFile(THREADS * count); + public void testMultipleAppendersLocked() throws Exception { + testMultipleAppenders(true); + } + + @Test + public void testMultipleAppendersUnlocked() throws Exception { + testMultipleAppenders(false); } @Test @Ignore public void testMultipleVMs() throws Exception { - final String classPath = System.getProperty("java.class.path"); final Integer count = 10; final int processes = 3; @@ -159,64 +547,110 @@ verifyFile(count * processes); } - private static void writer(final boolean lock, final int count, final String name) throws Exception { - final Layout layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN).build(); - final FileAppender app = FileAppender.createAppender(FILENAME, "true", Boolean.toString(lock), "test", "false", - "false", "false", null, layout, null, "false", null, null); - app.start(); - assertTrue("Appender did not start", app.isStarted()); - for (int i = 0; i < count; ++i) { - final LogEvent event = new Log4jLogEvent("TestLogger", null, FileAppenderTest.class.getName(), Level.INFO, - new SimpleMessage("Test"), null, null, null, name, null, System.currentTimeMillis()); - try { - app.append(event); - Thread.sleep(25); // Give up control long enough for another thread/process to occasionally do - // something. - } catch (final Exception ex) { - throw ex; + static void writer(final boolean lock, final int count, final String name, + AbstractCipherConfiguration cipherConfiguration) throws Exception { + final Layout layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN) + .build(); + final FileAppender appender = FileAppender.createAppender(FILENAME, "true", Boolean.toString(lock), "test", + "false", "false", "false", null, layout, null, "false", null, cipherConfiguration, null); + try { + appender.start(); + assertTrue("Appender did not start", appender.isStarted()); + for (int i = 0; i < count; ++i) { + final LogEvent event = new Log4jLogEvent("TestLogger", null, FileAppenderTest.class.getName(), + Level.INFO, new SimpleMessage("This is a simple message"), null, null, null, name, null, + System.currentTimeMillis()); + try { + appender.append(event); + Thread.sleep(25); // Give up control long enough for another thread/process to occasionally do + // something. + } catch (final Exception ex) { + throw ex; + } } + } finally { + appender.stop(); } - app.stop(); - assertFalse("Appender did not stop", app.isStarted()); + assertFalse("Appender did not stop", appender.isStarted()); } - private void verifyFile(final int count) throws Exception { - // String expected = "[\\w]* \\[\\s*\\] INFO TestLogger - Test$"; - final String expected = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3} \\[[^\\]]*\\] INFO TestLogger - Test"; - final Pattern pattern = Pattern.compile(expected); - final FileInputStream fis = new FileInputStream(FILENAME); - final BufferedReader is = new BufferedReader(new InputStreamReader(fis)); - int counter = 0; - String str = Strings.EMPTY; - while (is.ready()) { - str = is.readLine(); - // System.out.println(str); - ++counter; - final Matcher matcher = pattern.matcher(str); - assertTrue("Bad data: " + str, matcher.matches()); + /** + * @throws Exception + */ + private void verifyFile(final int expectedLineCount) throws Exception { + if (this.cipherEncrypt == null) { + this.verifyClearTextFile(expectedLineCount, FILENAME); + } else { + this.verifyEncryptedFile(expectedLineCount); } - fis.close(); - assertTrue("Incorrect count: was " + counter + " should be " + count, count == counter); - fis.close(); + } + private void verifyEncryptedFile(final int expectedLineCount) throws Exception { + if (this.cipherConfigurationDecrypt == null) { + return; + } + final File file = new File(FILENAME); + assertTrue("Hey, the file " + file.getAbsolutePath() + " does not exist! " + this, file.exists()); + assertFalse("Hey, the file " + file.getAbsolutePath() + " is empty! " + this, file.length() == 0); + final byte[] encBytes = FileUtils.readFileToByteArray(file); + final byte[] decBytes = this.cipherConfigurationDecrypt.newCipher().doFinal(encBytes); + FileUtils.writeByteArrayToFile(new File(FILENAME_DECRYPTED), decBytes); + this.verifyClearTextFile(expectedLineCount, FILENAME_DECRYPTED); + } + + private void verifyClearTextFile(final int expectedLineCount, final String logFileName) throws Exception { + // String expected = "[\\w]* \\[\\s*\\] INFO TestLogger - Test$"; + final String expected = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3} \\[[^\\]]*\\] INFO TestLogger - This is a simple message"; + final Pattern pattern = Pattern.compile(expected); + final File file = new File(logFileName); + assertFalse("Hey, the file " + logFileName + "is empty!", file.length() == 0); + final FileInputStream fis = new FileInputStream(file); + int counter = 0; + boolean padding = false; + try { + final BufferedReader reader = new BufferedReader(new InputStreamReader(fis)); + String str = Strings.EMPTY; + while (reader.ready()) { + str = reader.readLine(); + // System.out.println(str); + ++counter; + if (!str.trim().isEmpty()) { + final Matcher matcher = pattern.matcher(str); + final boolean matches = matcher.matches(); + assertTrue("Bad data: '" + str + "' no match for '" + expected + "', cipherConfigurationEncrypt=" + + this.cipherConfigurationEncrypt + ", cipherConfigurationDecrypt=" + + this.cipherConfigurationDecrypt, matches); + } else { + // TODO + // For encrypted files padding at the end of a stream can have blanks to round out the last buffer + // to the block size. There must be a better way to deal with this case. + padding = true; + } + } + } finally { + fis.close(); + } + int expectedLineCountWPadding = padding ? expectedLineCount + 1 : expectedLineCount; + assertTrue("Incorrect count: was " + counter + " should be " + expectedLineCountWPadding, + expectedLineCountWPadding == counter); } public class FileWriterRunnable implements Runnable { private final boolean lock; private final int count; + private final AbstractCipherConfiguration cipherConfiguration; - public FileWriterRunnable(final boolean lock, final int count) { + public FileWriterRunnable(final boolean lock, final int count, AbstractCipherConfiguration cipherConfiguration) { this.lock = lock; this.count = count; + this.cipherConfiguration = cipherConfiguration; } @Override public void run() { final Thread thread = Thread.currentThread(); - try { - writer(lock, count, thread.getName()); - + writer(lock, count, thread.getName(), cipherConfiguration); } catch (final Exception ex) { throw new RuntimeException(ex); } @@ -244,7 +678,7 @@ // System.out.println("Got arguments " + id + ", " + count + ", " + lock); try { - writer(lock, count, id); + writer(lock, count, id, null); // thread.sleep(50); } catch (final Exception ex) { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java index 72c81a4..d8f3af0 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java @@ -81,7 +81,7 @@ .withConfiguration(config) .build(); final Appender appender = FileAppender.createAppender(LOG_FILE, "false", "false", "File", "true", - "false", "false", "4000", layout, null, "false", null, config); + "false", "false", "4000", layout, null, "false", null, null, config); appender.start(); config.addAppender(appender); final AppenderRef ref = AppenderRef.createAppenderRef("File", null, null); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ByteArrayParserTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ByteArrayParserTest.java new file mode 100644 index 0000000..fb99840 --- /dev/null +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ByteArrayParserTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.core.config.plugins.util; + +import java.nio.charset.Charset; + +import org.apache.commons.codec.DecoderException; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Tests {@link ByteArrayParser}. + */ +public class ByteArrayParserTest { + + /** + * @return + * @throws DecoderException + */ + private byte[] parse(final String value) throws Exception { + return ByteArrayParser.parse(value); + } + + @Test + public void test0x() throws Exception { + assertArrayEquals(new byte[] { 0 }, this.parse("0x00")); + assertArrayEquals(new byte[] { 1 }, this.parse("0x01")); + assertArrayEquals(new byte[] { -1 }, this.parse("0xFF")); + assertArrayEquals(new byte[] { + -1, + 0, + -3 }, this.parse("0xFF00FD")); + } + + @Test + public void testBase64() throws Exception { + assertArrayEquals("pleasure.".getBytes("US-ASCII"), this.parse("Base64:cGxlYXN1cmUu")); + } + + @Test + public void testString() throws Exception { + assertArrayEquals("Hello".getBytes(Charset.defaultCharset()), this.parse("Hello")); + } +} diff --git a/log4j-core/src/test/resources/crypto/private-key-pkcs8.txt b/log4j-core/src/test/resources/crypto/private-key-pkcs8.txt new file mode 100644 index 0000000..de3d97b --- /dev/null +++ b/log4j-core/src/test/resources/crypto/private-key-pkcs8.txt @@ -0,0 +1,29 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE6jAcBgoqhkiG9w0BDAEEMA4ECEao95LyNN58AgICxgSCBMh7x9Gi1938vcEL +Rlkfz9RkpvbxNZvWoXBW2agXRUsJLj9w6zhRDLzeuGY03R1o1saOT29XY+jmn7L6 +ZEXVny0/DoQVcBZrF6CtuAwMqPB1V3ScfefpbycBviYG9ZPIYsttQZA8h1wsYCXg +YefBZ7yV/clVJp+cJP8JgHllh4Yx/EmLe/P7GffukRn/TICCICwcHU/xJbyPuBmL +IdazI7dn5rPrjUuCKT6p6PYrvkoCkNkAk0xJ7xmU351ESYOc4P0F7QgXoS4AawDG +5cZPzfd3Q5xalU9ejKtK13SeXoY6m8xRWO5LCL91RuMSx/6vUXtuZafYONN5VJ8a +lS/Ivbh9MiG49mA7wUV0DXfa3IvZOa4AIjf4KT0dbmbOdH+AvKWJAdI7RVnuO1Qp +dezYYOGwKQd1J87+CA/WDimvHmkoqatyRsLyMWTlKIxptqyrtQ054k7xeP5vOUKE +dkZwzSI8CszFhjTGz6YlEUBTl7Ea/nZIEUFnufzjtUkI2T9AxJGRNUolfPqYiRof +oDQtCXBOc0MdgloeA5lwW+FJFnt4a6P3Ncoc583zz2NYN43w5pl1TRloiHLrOEQx +bAwYND7NOcyrFratjK5gCWUIJRgUiA6KgiTpT3VipcX21QOuj8dNkNt80fsqmLiX +Lt+PPi//L1bOjZNvPRyvkjrrETrRf4D+TpzPkZ8CRgFdh27ik74zVB+J21nKR31z +lzldOhRRn0jTb5Yrm2MJbXf5mPJYxBTChDcb4J+77esPklJfLFriZ+3kMHdqXDkw +tytFdH46jpGJhLXZ5aHk3eb/0mJiaap1aq0BxIAXeHqXXTzTD+O8nCKKczX8j5um +PkOmYo1zzDSMRN1tcdaBORCH7LtSEWaHocErWJMyvlOCyvLEXP6zV9T0gxyUUSw/ +LjotB6nyUi1TqzJYi/WKN/zUwjcT6GlU7w+2uwH5Pz3Gaie5hXp++EuBloxoNkq/ +VzNWTPgb2vOSw89wr7awl1IWIIJmiIhvd3NSDd9q5bE1YTTBSsw1RvwSVXaU4plB +Iy/SGeVNV8+YkV1jbmTyiBvF+7WgURM5df7pG69kuJnQFy5pIZ6QwUFy5DDlNMBW +ctt9IQz7z0nrwWW1EBbUEMi60V6JoW0swAnL28M2vAOOAG/AWNGhGeOOHuPa3/by +nYVzSgSNbets7nXW++2fNSkOnBzLRiCJPw7ZvrJGYOeYby2JXa7eMl95GT2nKWsP +Ma4cnHyGF6uQ1jRJaQBVr/O1YVmLUdVnKOlBlALvDlCBwWj/xojuZa4XsO4VRGXH +QADHTdTyhQAXzMA6MSZFghWIqTi23UbWohYslZNNbOu3LlE0+XkNmSf9lH+7g6mt +GGdI2lBTi7E/KJW7y8vx7OCzP+ctPrn7tdzXsfOwEFxTtb625VxURFb0lcF3HAoh +xKpxcG3k3/bGFrt6ogQTn1uKPSX7ZDb4xOtL6HUbWUPwK19r5A5hopg2Qz+rnlXK +2KWrlXK+nLS+vrx4REeRJvfe9cURXk3vjnEbC7ao4z3iXC3Ux79GZXM9EkJB2B2d +tsWDA213q3HM4NbPV9Tu+alYefG0stzuBpmSIUnXjq+YWpU/RgjDQQ3HhSBbe4qz +tbSodITs/in7cgCoypQ= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/log4j-core/src/test/resources/crypto/sample-cert.txt b/log4j-core/src/test/resources/crypto/sample-cert.txt new file mode 100644 index 0000000..cd95e9b --- /dev/null +++ b/log4j-core/src/test/resources/crypto/sample-cert.txt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC/DCCAeSgAwIBAgIEU4VpIjANBgkqhkiG9w0BAQsFADBAMQswCQYDVQQGEwJV +UzEOMAwGA1UECgwFTG9nNGoxEDAOBgNVBAsMB0xvZ2dpbmcxDzANBgNVBAMMBkFw +YWNoZTAeFw0xNDA1MjgwNDQyNTBaFw0xNTA1MjgwNDQyNTBaMEAxCzAJBgNVBAYT +AlVTMQ4wDAYDVQQKDAVMb2c0ajEQMA4GA1UECwwHTG9nZ2luZzEPMA0GA1UEAwwG +QXBhY2hlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiC0CgbEQn9Hj +LIL9VZwBD+JbzT3wKCLgaX8JpbBvpBxCTFaLgtxyTgXs7IC/IqmzL5Q4NpZ6CIGx +BeMoM5i+kEDJ4peftSHcD4poSh8go/oZKc1NcdQPTDPhrIn69sAR+teZUsOlEICW +c9rSe567S/l4+6SRJRHXDatDRYrd6JYK0gJHQR3lDpEXjyCLMDgQUTyDeRiBGG7q +HWt+5dLhekZhLxGwjmklPvpop7Sc4kjwo7mZSc+C5W8NsuXYJaX+T/mMKDglU6nG +eIRmh2EhPdiz8Aoj5ElHjXNo0eOtdZ0UdqL56WvVOg807uJ5SR8fI/YaJSul7sfl +VHVr0shSywIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAJRAXsBh6wP0Bo5NjH2NFb +ettmbqzRZA5fvFQryez9sfhhJxlFa0iOBsFKqQPZv81Cviz1WV4Bhoei0Y5aXu5g +zmVB8BWbYVj68qg1jxv6t8Bp1fYjz+MAfK23XgP4Fe436Vf0pcpmszakKT6/zD9+ +7etxOOhLbR6bVblOpXM02C+yluKashRi4BNNhdCJj+C+HtwCDsS7oiNkc2AkNXfD +vZRtT0pF2LImA8ZxIUY1qIY3DTNjRHdx6sFA2sPVpvooB15LaIGliaFONliLtRVY +a1yq7Np2Lw4e+sRSlthP5LzQqiJN5ibeNyMWEoUo0duYjzZJZmHB4kRqdSfcHL8m +-----END CERTIFICATE-----