diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TestConstants.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TestConstants.java deleted file mode 100644 index e3c0fe6010f..00000000000 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TestConstants.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.test.net.ssl; - -public class TestConstants { - - public static final String SOURCE_FOLDER = "src/test/resources/"; - public static final String RESOURCE_ROOT = "org/apache/logging/log4j/core/net/ssl/"; - - public static final String PATH = SOURCE_FOLDER + RESOURCE_ROOT; - public static final String TRUSTSTORE_PATH = PATH; - public static final String TRUSTSTORE_RESOURCE = RESOURCE_ROOT; - public static final String TRUSTSTORE_FILE = TRUSTSTORE_PATH + "truststore.jks"; - public static final String TRUSTSTORE_FILE_RESOURCE = TRUSTSTORE_RESOURCE + "truststore.jks"; - - public static final char[] TRUSTSTORE_PWD() { - return "changeit".toCharArray(); - } - - public static final String TRUSTSTORE_TYPE = "JKS"; - - public static final String KEYSTORE_PATH = PATH; - public static final String KEYSTORE_RESOURCE = RESOURCE_ROOT; - public static final String KEYSTORE_FILE = KEYSTORE_PATH + "client.log4j2-keystore.jks"; - public static final String KEYSTORE_FILE_RESOURCE = KEYSTORE_RESOURCE + "client.log4j2-keystore.jks"; - - public static final char[] KEYSTORE_PWD() { - return "changeit".toCharArray(); - } - - public static final String KEYSTORE_TYPE = "JKS"; - - public static final char[] NULL_PWD = null; -} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/ListErrorHandler.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/BufferingErrorHandler.java similarity index 50% rename from log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/ListErrorHandler.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/BufferingErrorHandler.java index d3f7fbc7e19..96c1635a18e 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/ListErrorHandler.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/BufferingErrorHandler.java @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.core.test; +package org.apache.logging.log4j.core.appender; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.stream.Stream; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.ErrorHandler; import org.apache.logging.log4j.core.LogEvent; @@ -26,47 +26,43 @@ import org.apache.logging.log4j.status.StatusData; import org.apache.logging.log4j.util.StackLocatorUtil; -public class ListErrorHandler implements ErrorHandler { +/** + * {@link ErrorHandler} implementation buffering invocations in the form of {@link StatusData}. + */ +final class BufferingErrorHandler implements ErrorHandler { + + private final List statusDataBuffer = Collections.synchronizedList(new ArrayList<>()); - private final ArrayList statusData = new ArrayList<>(); + BufferingErrorHandler() {} - private void addStatusData(final String msg, final Throwable t) { - synchronized (statusData) { - final StackTraceElement caller = StackLocatorUtil.getStackTraceElement(3); - final String threadName = Thread.currentThread().getName(); - statusData.add(new StatusData(caller, Level.ERROR, new SimpleMessage(msg), t, threadName)); - } + private void addStatusData(final String message, final Throwable throwable) { + final StackTraceElement caller = StackLocatorUtil.getStackTraceElement(3); + final String threadName = Thread.currentThread().getName(); + final StatusData statusData = + new StatusData(caller, Level.ERROR, new SimpleMessage(message), throwable, threadName); + statusDataBuffer.add(statusData); } @Override - public void error(String msg) { - addStatusData(msg, null); + public void error(String message) { + addStatusData(message, null); } @Override - public void error(String msg, Throwable t) { - addStatusData(msg, t); + public void error(final String message, final Throwable throwable) { + addStatusData(message, throwable); } @Override - public void error(String msg, LogEvent event, Throwable t) { - addStatusData(msg, t); + public void error(final String message, final LogEvent event, final Throwable throwable) { + addStatusData(message, throwable); } public void clear() { - synchronized (statusData) { - statusData.clear(); - } - } - - public Stream getStatusData() { - synchronized (statusData) { - return ((List) statusData.clone()).stream(); - } + statusDataBuffer.clear(); } - public Stream findStatusData(String regex) { - return getStatusData() - .filter(data -> data.getMessage().getFormattedMessage().matches(regex)); + public List getBuffer() { + return statusDataBuffer; } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FixedHostResolver.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FixedHostResolver.java new file mode 100644 index 00000000000..894d710a6a2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FixedHostResolver.java @@ -0,0 +1,47 @@ +/* + * 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; + +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.logging.log4j.core.net.TcpSocketManager; + +/** + * {@link TcpSocketManager.HostResolver} implementation always resolving to the given list of {@link #addresses}. + */ +final class FixedHostResolver extends TcpSocketManager.HostResolver { + + private final List addresses; + + private FixedHostResolver(final List addresses) { + this.addresses = addresses; + } + + static FixedHostResolver ofServers(final LineReadingTcpServer... servers) { + final List addresses = Arrays.stream(servers) + .map(server -> (InetSocketAddress) server.getServerSocket().getLocalSocketAddress()) + .collect(Collectors.toList()); + return new FixedHostResolver(addresses); + } + + @Override + public List resolveHost(final String ignoredHost, final int ignoredPort) { + return addresses; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/LineReadingTcpServer.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/LineReadingTcpServer.java new file mode 100644 index 00000000000..aa5119164a4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/LineReadingTcpServer.java @@ -0,0 +1,227 @@ +/* + * 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; + +import static org.awaitility.Awaitility.await; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.net.ServerSocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * A simple TCP server implementation reading the accepted connection's input stream into a blocking queue of lines. + *

+ * The implementation is thread-safe, yet connections are handled sequentially, i.e., no parallelization. + * The input stream of the connection is decoded in UTF-8. + *

+ * This class can also be used for secure (i.e., SSL) connections. + * You need to pass the {@link ServerSocketFactory} obtained from {@link SSLContext#getServerSocketFactory()} to the appropriate constructor. + *

+ */ +final class LineReadingTcpServer implements AutoCloseable { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final ServerSocketFactory serverSocketFactory; + + private volatile boolean running; + + private ServerSocket serverSocket; + + private Socket clientSocket; + + private Thread readerThread; + + private final BlockingQueue lines = new LinkedBlockingQueue<>(); + + LineReadingTcpServer() { + this(ServerSocketFactory.getDefault()); + } + + LineReadingTcpServer(final ServerSocketFactory serverSocketFactory) { + this.serverSocketFactory = serverSocketFactory; + } + + synchronized void start(final String name, final int port) throws IOException { + if (!running) { + running = true; + serverSocket = createServerSocket(port); + readerThread = createReaderThread(name); + } + } + + private ServerSocket createServerSocket(final int port) throws IOException { + final ServerSocket serverSocket = serverSocketFactory.createServerSocket(port); + serverSocket.setReuseAddress(true); + serverSocket.setSoTimeout(0); // Zero indicates `accept()` will block indefinitely + await("server socket binding") + .pollInterval(100, TimeUnit.MILLISECONDS) + .atMost(120, TimeUnit.SECONDS) + .until(() -> serverSocket.getLocalPort() != -1 && serverSocket.isBound()); + return serverSocket; + } + + private Thread createReaderThread(final String name) { + final String threadName = "LineReadingTcpSocketServerReader-" + name; + final Thread thread = new Thread(this::acceptClients, threadName); + thread.setDaemon(true); // Avoid blocking JVM exit + thread.setUncaughtExceptionHandler((ignored, error) -> LOGGER.error("uncaught reader thread exception", error)); + thread.start(); + return thread; + } + + private void acceptClients() { + try { + while (running) { + acceptClient(); + } + } catch (final Exception error) { + LOGGER.error("failed accepting client connections", error); + } + } + + private void acceptClient() throws Exception { + + // Accept the client connection + final Socket clientSocket; + try { + clientSocket = serverSocket.accept(); + } catch (SocketException ignored) { + return; + } + clientSocket.setSoLinger(true, 0); // Enable immediate forceful close + synchronized (this) { + if (running) { + this.clientSocket = clientSocket; + } + } + + // Read from the client + try (final InputStream clientInputStream = clientSocket.getInputStream(); + final InputStreamReader clientReader = + new InputStreamReader(clientInputStream, StandardCharsets.UTF_8); + final BufferedReader clientBufferedReader = new BufferedReader(clientReader)) { + while (running) { + final String line = clientBufferedReader.readLine(); + if (line == null) { + break; + } + lines.put(line); + } + } catch (final SSLHandshakeException | EOFException error) { + // Ignore `EOFException`s + if (!(error.getCause() instanceof EOFException)) { + throw error; + } + } + + // Ignore connection failures. + catch (final SocketException ignored) { + } + + // Clean up the client connection. + finally { + try { + synchronized (this) { + if (!clientSocket.isClosed()) { + clientSocket.shutdownOutput(); + clientSocket.close(); + } + this.clientSocket = null; + } + } catch (final Exception error) { + LOGGER.error("failed closing client socket", error); + } + } + } + + @Override + public void close() throws Exception { + + // Stop the reader, if running + Thread stoppedReaderThread = null; + synchronized (this) { + if (running) { + running = false; + // `acceptClient()` might have closed the client socket due to a connection failure and haven't created + // a new one yet. Hence, here we double-check if the client connection is in place. + if (clientSocket != null && !clientSocket.isClosed()) { + // Interrupting a thread is not sufficient to unblock operations waiting on socket I/O: + // https://stackoverflow.com/a/4426050/1278899 Hence, here we close the client socket to unblock the + // read from the client socket. + clientSocket.close(); + } + serverSocket.close(); + stoppedReaderThread = readerThread; + clientSocket = null; + serverSocket = null; + readerThread = null; + } + } + + // We wait for the termination of the reader thread outside the synchronized block. Otherwise, there is a chance + // of deadlock with this `join()` and the synchronized block inside the `acceptClient()`. + if (stoppedReaderThread != null) { + stoppedReaderThread.join(); + } + } + + ServerSocket getServerSocket() { + return serverSocket; + } + + List pollLines(@SuppressWarnings("SameParameterValue") final int count) throws InterruptedException { + final List polledLines = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + final String polledLine; + try { + polledLine = pollLine(); + } catch (final TimeoutException timeout) { + final String message = + String.format("timeout while polling for line %d (total needed: %d)", (i + 1), count); + throw new RuntimeException(message, timeout); + } + polledLines.add(polledLine); + } + return polledLines; + } + + private String pollLine() throws InterruptedException, TimeoutException { + final String line = lines.poll(2, TimeUnit.SECONDS); + if (line == null) { + throw new TimeoutException(); + } + return line; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderReconnectTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderReconnectTest.java new file mode 100644 index 00000000000..ba590cfe259 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderReconnectTest.java @@ -0,0 +1,398 @@ +/* + * 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; + +import static org.apache.logging.log4j.core.appender.SslContexts.createSslContext; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.fail; + +import edu.umd.cs.findbugs.annotations.Nullable; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.net.ssl.SSLContext; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; +import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.net.TcpSocketManager; +import org.apache.logging.log4j.core.net.TcpSocketManager.HostResolver; +import org.apache.logging.log4j.core.net.ssl.SslKeyStoreConstants; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +/** + * Tests reconnection support of {@link org.apache.logging.log4j.core.appender.SocketAppender}. + */ +public class SocketAppenderReconnectTest { + + private static final int EPHEMERAL_PORT = 0; + + private static final String APPENDER_NAME = "TestSocket"; + + /** + * Tests if failures are propagated when reconnection fails. + * + * @see LOG4J2-2829 + */ + @Test + @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure + void repeating_reconnect_failures_should_be_propagated() throws Exception { + try (final LineReadingTcpServer server = new LineReadingTcpServer()) { + + // Start the server. + server.start("Main", EPHEMERAL_PORT); + final int port = server.getServerSocket().getLocalPort(); + + // Initialize the logger context + final Configuration config = createConfiguration(port, null); + try (final LoggerContext loggerContext = createStartedLoggerContext(config)) { + + // Configure the error handler + final BufferingErrorHandler errorHandler = new BufferingErrorHandler(); + loggerContext.getConfiguration().getAppender(APPENDER_NAME).setHandler(errorHandler); + + // Verify the initial working state. + verifyLoggingSuccess(loggerContext, server, errorHandler); + + // Stop the server, and verify the logging failure. + server.close(); + verifyLoggingFailure(loggerContext, errorHandler); + + // Start the server again, and verify the logging success. + server.start("Main", port); + verifyLoggingSuccess(loggerContext, server, errorHandler); + } + } + } + + /** + * Tests if all the {@link InetSocketAddress}es returned by an {@link HostResolver} is used for fallback on reconnect attempts. + */ + @Test + @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure + void reconnect_should_fallback_when_there_are_multiple_resolved_hosts() throws Exception { + try (final LineReadingTcpServer primaryServer = new LineReadingTcpServer(); + final LineReadingTcpServer secondaryServer = new LineReadingTcpServer()) { + + // Start servers. + primaryServer.start("Primary", EPHEMERAL_PORT); + secondaryServer.start("Secondary", EPHEMERAL_PORT); + + // Mock the host resolver. + final FixedHostResolver hostResolver = FixedHostResolver.ofServers(primaryServer, secondaryServer); + TcpSocketManager.setHostResolver(hostResolver); + try { + + // Initialize the logger context + final Configuration config = createConfiguration( + // Passing an invalid port, since the resolution is supposed to be performed by the mocked host + // resolver anyway. + 0, null); + try (final LoggerContext loggerContext = createStartedLoggerContext(config)) { + + // Configure the error handler + final BufferingErrorHandler errorHandler = new BufferingErrorHandler(); + loggerContext.getConfiguration().getAppender(APPENDER_NAME).setHandler(errorHandler); + + // Verify the initial working state on the primary server. + verifyLoggingSuccess(loggerContext, primaryServer, errorHandler); + + // Stop the primary server, and verify the logging success due to fallback on to the secondary + // server. + primaryServer.close(); + verifyLoggingSuccess(loggerContext, secondaryServer, errorHandler); + } + + } + + // Reset the host resolver + finally { + TcpSocketManager.setHostResolver(new HostResolver()); + } + } + } + + /** + * Triggers a reconfiguration such that the {@code } and {@code } configuration will be unchanged, but the content they refer to will be updated. + * + * @see LOG4J2-2988 + * @see #2767 + */ + @Test + void key_store_changes_should_be_detected_at_reconfigure( + @TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path tempDir) throws Exception { + + // Create the 1st `SSLContext` + final String keyStore1Type = SslKeyStoreConstants.KEYSTORE_TYPE; + final String keyStore1Location = SslKeyStoreConstants.KEYSTORE_LOCATION; + final char[] keyStore1Password = SslKeyStoreConstants.KEYSTORE_PWD(); + final String trustStore1Type = SslKeyStoreConstants.TRUSTSTORE_TYPE; + final String trustStore1Location = SslKeyStoreConstants.TRUSTSTORE_LOCATION; + final char[] trustStore1Password = SslKeyStoreConstants.TRUSTSTORE_PWD(); + final SSLContext sslContext1 = createSslContext( + keyStore1Type, + keyStore1Location, + keyStore1Password, + trustStore1Type, + trustStore1Location, + trustStore1Password); + + // Create the 2nd `SSLContext` + final String keyStore2Type = SslKeyStoreConstants.KEYSTORE2_TYPE; + final String keyStore2Location = SslKeyStoreConstants.KEYSTORE2_LOCATION; + final char[] keyStore2Password = SslKeyStoreConstants.KEYSTORE2_PWD(); + final String trustStore2Type = SslKeyStoreConstants.TRUSTSTORE2_TYPE; + final String trustStore2Location = SslKeyStoreConstants.TRUSTSTORE2_LOCATION; + final char[] trustStore2Password = SslKeyStoreConstants.TRUSTSTORE2_PWD(); + final SSLContext sslContext2 = createSslContext( + keyStore2Type, + keyStore2Location, + keyStore2Password, + trustStore2Type, + trustStore2Location, + trustStore2Password); + + // Ensure that store types are identical. + // We will use these in the `*StoreConfiguration`. + // They need to be same, so that the encapsulating `SslConfiguration` will be unchanged during reconfiguration. + assertThat(keyStore1Type).isEqualTo(keyStore2Type); + + // Stage the key store files using the 1st `SSLContext` + @SuppressWarnings("UnnecessaryLocalVariable") + final String keyStoreType = keyStore1Type; + final Path keyStoreFilePath = tempDir.resolve("keyStore"); + Files.write(keyStoreFilePath, Files.readAllBytes(Paths.get(keyStore1Location))); + final Path keyStorePasswordFilePath = tempDir.resolve("keyStorePassword"); + Files.write(keyStorePasswordFilePath, new String(keyStore1Password).getBytes(StandardCharsets.UTF_8)); + + // Stage the trust store files using the 1st `SSLContext` + @SuppressWarnings("UnnecessaryLocalVariable") + final String trustStoreType = trustStore1Type; + final Path trustStoreFilePath = tempDir.resolve("trustStore"); + Files.write(trustStoreFilePath, Files.readAllBytes(Paths.get(trustStore1Location))); + final Path trustStorePasswordFilePath = tempDir.resolve("trustStorePassword"); + Files.write(trustStorePasswordFilePath, new String(trustStore1Password).getBytes(StandardCharsets.UTF_8)); + + // Create servers + try (final LineReadingTcpServer server1 = new LineReadingTcpServer(sslContext1.getServerSocketFactory()); + final LineReadingTcpServer server2 = new LineReadingTcpServer(sslContext2.getServerSocketFactory())) { + + // Start the 1st server + server1.start("1st", EPHEMERAL_PORT); + final int port = server1.getServerSocket().getLocalPort(); + + // Create the configuration transformer to add the ``, ``, and `` elements + final BiFunction< + ConfigurationBuilder, + AppenderComponentBuilder, + AppenderComponentBuilder> + appenderComponentBuilderTransformer = (configBuilder, appenderComponentBuilder) -> { + final ComponentBuilder keyStoreComponentBuilder = configBuilder + .newComponent("KeyStore") + .addAttribute("type", keyStoreType) + .addAttribute("location", keyStoreFilePath.toString()) + .addAttribute("passwordFile", keyStorePasswordFilePath); + final ComponentBuilder trustStoreComponentBuilder = configBuilder + .newComponent("TrustStore") + .addAttribute("type", trustStoreType) + .addAttribute("location", trustStoreFilePath.toString()) + .addAttribute("passwordFile", trustStorePasswordFilePath); + return appenderComponentBuilder.addComponent(configBuilder + .newComponent("Ssl") + .addAttribute("protocol", "TLS") + .addComponent(keyStoreComponentBuilder) + .addComponent(trustStoreComponentBuilder)); + }; + + // Initialize the logger context + final Configuration config1 = createConfiguration(port, appenderComponentBuilderTransformer); + try (final LoggerContext loggerContext = createStartedLoggerContext(config1)) { + + // Configure the error handler + final BufferingErrorHandler errorHandler = new BufferingErrorHandler(); + loggerContext.getConfiguration().getAppender(APPENDER_NAME).setHandler(errorHandler); + + // Verify the initial working state on the 1st server + verifyLoggingSuccess(loggerContext, server1, errorHandler); + + // Stop the 1st server and start the 2nd one (using different SSL configuration!) on the same port + server1.close(); + server2.start("2nd", port); + + // Stage the key store files using the 2nd `SSLContext` + Files.write(keyStoreFilePath, Files.readAllBytes(Paths.get(keyStore2Location))); + Files.write(keyStorePasswordFilePath, new String(keyStore2Password).getBytes(StandardCharsets.UTF_8)); + + // Stage the trust store files using the 2nd `SSLContext` + Files.write(trustStoreFilePath, Files.readAllBytes(Paths.get(trustStore2Location))); + Files.write( + trustStorePasswordFilePath, new String(trustStore2Password).getBytes(StandardCharsets.UTF_8)); + + // Reconfigure the logger context + // + // You might be thinking: + // + // Why don't we simply call `loggerContext.reconfigure()`? + // Why do we need to create the very same configuration twice? + // + // We need to reconfigure the logger context using the very same configuration to test if + // `SslSocketManager` will be able to pick up the key and trust store changes even though the + // `SslConfiguration` is untouched – the whole point of this test and the issue reported in LOG4J2-2988. + // + // `loggerContext.reconfigure()` stops the active configuration (i.e., the programmatically built + // configuration we provided to it), and starts a fresh scan for `log4j2.xml` et al. in the classpath. + // This effectively discards the initially provided configuration. + // + // `loggerContext.reconfigure(loggerContext.getConfiguration())` doesn't work either, since it tries to + // start the configuration it has just stopped and a programmatically built `Configuration` is not + // `Reconfigurable` – yes, this needs to be fixed. + // + // Hence, the only way is to programmatically build the very same configuration, twice, and use the 1st + // one for initialization, and the 2nd one for reconfiguration. + final Configuration config2 = createConfiguration(port, appenderComponentBuilderTransformer); + loggerContext.reconfigure(config2); + + // Verify the working state on the 2nd server + verifyLoggingSuccess(loggerContext, server2, errorHandler); + } + } + } + + private static Configuration createConfiguration( + final int port, + @Nullable + final BiFunction< + ConfigurationBuilder, + AppenderComponentBuilder, + AppenderComponentBuilder> + appenderComponentBuilderTransformer) { + + // Create the configuration builder + final ConfigurationBuilder configBuilder = + ConfigurationBuilderFactory.newConfigurationBuilder() + .setStatusLevel(Level.ERROR) + .setConfigurationName(SocketAppenderReconnectTest.class.getSimpleName()); + + // Create the appender configuration + final AppenderComponentBuilder appenderComponentBuilder = configBuilder + .newAppender(APPENDER_NAME, "Socket") + .addAttribute("host", "localhost") + .addAttribute("port", port) + .addAttribute("ignoreExceptions", false) + .addAttribute("reconnectionDelayMillis", 10) + .addAttribute("immediateFlush", true) + .add(configBuilder.newLayout("PatternLayout").addAttribute("pattern", "%m%n")); + final AppenderComponentBuilder transformedAppenderComponentBuilder = appenderComponentBuilderTransformer != null + ? appenderComponentBuilderTransformer.apply(configBuilder, appenderComponentBuilder) + : appenderComponentBuilder; + + // Create the configuration + return configBuilder + .add(transformedAppenderComponentBuilder) + .add(configBuilder.newRootLogger(Level.ALL).add(configBuilder.newAppenderRef(APPENDER_NAME))) + .build(false); + } + + private static final AtomicInteger LOGGER_CONTEXT_COUNTER = new AtomicInteger(); + + private static LoggerContext createStartedLoggerContext(final Configuration configuration) { + final String name = String.format( + "%s-%02d", SocketAppenderReconnectTest.class.getSimpleName(), LOGGER_CONTEXT_COUNTER.getAndIncrement()); + final LoggerContext loggerContext = new LoggerContext(name, null, (String) null, DI.createInitializedFactory()); + loggerContext.start(configuration); + return loggerContext; + } + + private static void verifyLoggingSuccess( + final LoggerContext loggerContext, + final LineReadingTcpServer server, + final BufferingErrorHandler errorHandler) + throws Exception { + + // Create messages to log + final int messageCount = 2; + assertThat(messageCount) + .as("expecting `messageCount > 1` due to LOG4J2-2829") + .isGreaterThan(1); + final List expectedMessages = IntStream.range(0, messageCount) + .mapToObj(messageIndex -> String.format("m%02d", messageIndex)) + .collect(Collectors.toList()); + + // Log the 1st message + // Due to socket initialization, the first `write()` might need some extra effort + final Logger logger = loggerContext.getRootLogger(); + await("first socket append") + .pollInterval(100, TimeUnit.MILLISECONDS) + .atMost(120, TimeUnit.SECONDS) + .until(() -> { + final String message = expectedMessages.get(0); + logger.info(message); + return true; + }); + + // Reset the error handler + errorHandler.clear(); + + // Log the rest of the messages + for (int messageIndex = 1; messageIndex < expectedMessages.size(); messageIndex++) { + final String message = expectedMessages.get(messageIndex); + logger.info(message); + } + + // Verify the messages received by the server + final List actualMessages = server.pollLines(messageCount); + assertThat(actualMessages).containsExactlyElementsOf(expectedMessages); + + // Verify the error handler state + assertThat(errorHandler.getBuffer()).isEmpty(); + } + + private static void verifyLoggingFailure( + final LoggerContext loggerContext, final BufferingErrorHandler errorHandler) { + final Logger logger = loggerContext.getRootLogger(); + final int retryCount = 3; + assertThat(retryCount) + .as("expecting `retryCount > 1` due to LOG4J2-2829") + .isGreaterThan(1); + for (int i = 0; i < retryCount; i++) { + try { + logger.info("should fail #" + i); + fail("should have failed #" + i); + } catch (final AppenderLoggingException ignored) { + assertThat(errorHandler.getBuffer()).hasSize(2 * (i + 1)); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSslSocketOptionsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSslSocketOptionsTest.java new file mode 100644 index 00000000000..5e68fd776ce --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSslSocketOptionsTest.java @@ -0,0 +1,203 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import javax.net.ssl.SSLContext; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; +import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.net.Rfc1349TrafficClass; +import org.apache.logging.log4j.core.net.SocketOptions; +import org.apache.logging.log4j.core.net.SocketPerformancePreferences; +import org.apache.logging.log4j.core.net.SslSocketManager; +import org.apache.logging.log4j.core.net.ssl.SslKeyStoreConstants; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +public class SocketAppenderSslSocketOptionsTest { + + private static final String APPENDER_NAME = "TestSocket"; + + // Socket performance preferences + + private static final int SOCKET_PERFORMANCE_PREFERENCE_BANDWIDTH = 100; + + private static final int SOCKET_PERFORMANCE_PREFERENCE_CONNECTION_TIME = 101; + + private static final int SOCKET_PERFORMANCE_PREFERENCE_LATENCY = 102; + + // Socket options + + private static final boolean SOCKET_OPTION_KEEP_ALIVE = false; + + private static final int SOCKET_OPTION_RECEIVE_BUFFER_SIZE = 10_000; + + private static final boolean SOCKET_OPTION_REUSE_ADDRESS = false; + + private static final Rfc1349TrafficClass SOCKET_OPTION_RFC1349_TRAFFIC_CLASS = Rfc1349TrafficClass.IPTOS_LOWCOST; + + private static final int SOCKET_OPTION_SEND_BUFFER_SIZE = 8_000; + + private static final int SOCKET_OPTION_LINGER = 12_345; + + private static final int SOCKET_OPTION_TIMEOUT = 54_321; + + private static final boolean SOCKET_OPTION_TCP_NO_DELAY = false; + + // Key & Trust store + + private static final String KEYSTORE_TYPE = SslKeyStoreConstants.KEYSTORE_TYPE; + + private static final String KEYSTORE_LOCATION = SslKeyStoreConstants.KEYSTORE_LOCATION; + + private static final char[] KEYSTORE_PWD = SslKeyStoreConstants.KEYSTORE_PWD(); + + private static final String TRUSTSTORE_TYPE = SslKeyStoreConstants.TRUSTSTORE_TYPE; + + private static final String TRUSTSTORE_LOCATION = SslKeyStoreConstants.TRUSTSTORE_LOCATION; + + private static final char[] TRUSTSTORE_PWD = SslKeyStoreConstants.TRUSTSTORE_PWD(); + + @Test + void socket_options_should_be_injected(@TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path tempDir) + throws Exception { + + // Create the SSL context + final SSLContext sslContext = SslContexts.createSslContext( + KEYSTORE_TYPE, KEYSTORE_LOCATION, KEYSTORE_PWD, TRUSTSTORE_TYPE, TRUSTSTORE_LOCATION, TRUSTSTORE_PWD); + + // Create the server + try (final LineReadingTcpServer server = new LineReadingTcpServer(sslContext.getServerSocketFactory())) { + + // Start the server + server.start("1st", 0); + final int port = server.getServerSocket().getLocalPort(); + + // Create the logger context + final Configuration config = createConfiguration(tempDir, port); + try (final LoggerContext loggerContext = Configurator.initialize(config)) { + + // Extract the socket manager + final SocketAppender appender = loggerContext.getConfiguration().getAppender(APPENDER_NAME); + final SslSocketManager manager = (SslSocketManager) appender.getManager(); + + // Verify socket options + final SocketOptions socketOptions = manager.getSocketOptions(); + assertThat(socketOptions.isKeepAlive()).isEqualTo(SOCKET_OPTION_KEEP_ALIVE); + assertThat(socketOptions.isOobInline()).isNull(); + assertThat(socketOptions.getReceiveBufferSize()).isEqualTo(SOCKET_OPTION_RECEIVE_BUFFER_SIZE); + assertThat(socketOptions.isReuseAddress()).isEqualTo(SOCKET_OPTION_REUSE_ADDRESS); + assertThat(socketOptions.getRfc1349TrafficClass()).isEqualTo(SOCKET_OPTION_RFC1349_TRAFFIC_CLASS); + assertThat(socketOptions.getActualTrafficClass()) + .isEqualTo(SOCKET_OPTION_RFC1349_TRAFFIC_CLASS.value()); + assertThat(socketOptions.getSendBufferSize()).isEqualTo(SOCKET_OPTION_SEND_BUFFER_SIZE); + assertThat(socketOptions.getSoLinger()).isEqualTo(SOCKET_OPTION_LINGER); + assertThat(socketOptions.getSoTimeout()).isEqualTo(SOCKET_OPTION_TIMEOUT); + assertThat(socketOptions.isTcpNoDelay()).isEqualTo(SOCKET_OPTION_TCP_NO_DELAY); + + // Verify socket performance preferences + final SocketPerformancePreferences performancePreferences = socketOptions.getPerformancePreferences(); + assertThat(performancePreferences.getBandwidth()).isEqualTo(SOCKET_PERFORMANCE_PREFERENCE_BANDWIDTH); + assertThat(performancePreferences.getConnectionTime()) + .isEqualTo(SOCKET_PERFORMANCE_PREFERENCE_CONNECTION_TIME); + assertThat(performancePreferences.getLatency()).isEqualTo(SOCKET_PERFORMANCE_PREFERENCE_LATENCY); + } + } + } + + private static Configuration createConfiguration(final Path tempDir, final int port) throws Exception { + + // Create the configuration builder + final ConfigurationBuilder configBuilder = + ConfigurationBuilderFactory.newConfigurationBuilder() + .setStatusLevel(Level.ERROR) + .setConfigurationName(SocketAppenderReconnectTest.class.getSimpleName()); + + // Stage the key store password file + final Path keyStorePasswordFilePath = tempDir.resolve("keyStorePassword"); + Files.write(keyStorePasswordFilePath, new String(KEYSTORE_PWD).getBytes(StandardCharsets.UTF_8)); + + // Stage the trust store password file + final Path trustStorePasswordFilePath = tempDir.resolve("trustStorePassword"); + Files.write(trustStorePasswordFilePath, new String(TRUSTSTORE_PWD).getBytes(StandardCharsets.UTF_8)); + + // Create the `SocketOptions` element + final ComponentBuilder socketPerformancePreferencesComponentBuilder = configBuilder + .newComponent("SocketPerformancePreferences") + .addAttribute("bandwidth", SOCKET_PERFORMANCE_PREFERENCE_BANDWIDTH) + .addAttribute("connectionTime", SOCKET_PERFORMANCE_PREFERENCE_CONNECTION_TIME) + .addAttribute("latency", SOCKET_PERFORMANCE_PREFERENCE_LATENCY); + final ComponentBuilder socketOptionsComponentBuilder = configBuilder + .newComponent("SocketOptions") + .addAttribute("keepAlive", SOCKET_OPTION_KEEP_ALIVE) + .addAttribute("receiveBufferSize", SOCKET_OPTION_RECEIVE_BUFFER_SIZE) + .addAttribute("reuseAddress", SOCKET_OPTION_REUSE_ADDRESS) + .addAttribute("rfc1349TrafficClass", SOCKET_OPTION_RFC1349_TRAFFIC_CLASS) + .addAttribute("sendBufferSize", SOCKET_OPTION_SEND_BUFFER_SIZE) + .addAttribute("soLinger", SOCKET_OPTION_LINGER) + .addAttribute("soTimeout", SOCKET_OPTION_TIMEOUT) + .addAttribute("tcpNoDelay", SOCKET_OPTION_TCP_NO_DELAY) + .addComponent(socketPerformancePreferencesComponentBuilder); + + // Create the `Ssl` element + final ComponentBuilder keyStoreComponentBuilder = configBuilder + .newComponent("KeyStore") + .addAttribute("type", KEYSTORE_TYPE) + .addAttribute("location", KEYSTORE_LOCATION) + .addAttribute("passwordFile", keyStorePasswordFilePath); + final ComponentBuilder trustStoreComponentBuilder = configBuilder + .newComponent("TrustStore") + .addAttribute("type", TRUSTSTORE_TYPE) + .addAttribute("location", TRUSTSTORE_LOCATION) + .addAttribute("passwordFile", trustStorePasswordFilePath); + final ComponentBuilder sslComponentBuilder = configBuilder + .newComponent("Ssl") + .addAttribute("protocol", "TLS") + .addComponent(keyStoreComponentBuilder) + .addComponent(trustStoreComponentBuilder); + + // Create the `Socket` element + final AppenderComponentBuilder appenderComponentBuilder = configBuilder + .newAppender(APPENDER_NAME, "Socket") + .addAttribute("host", "localhost") + .addAttribute("port", port) + .addAttribute("ignoreExceptions", false) + .addAttribute("reconnectionDelayMillis", 10) + .addAttribute("immediateFlush", true) + .add(configBuilder.newLayout("PatternLayout").addAttribute("pattern", "%m%n")) + .addComponent(socketOptionsComponentBuilder) + .addComponent(sslComponentBuilder); + + // Create the configuration + return configBuilder + .add(appenderComponentBuilder) + .add(configBuilder.newRootLogger(Level.ALL).add(configBuilder.newAppenderRef(APPENDER_NAME))) + .build(false); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SslContexts.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SslContexts.java new file mode 100644 index 00000000000..5d5b5a2dd9f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SslContexts.java @@ -0,0 +1,64 @@ +/* + * 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; + +import java.io.ByteArrayInputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyStore; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +final class SslContexts { + + static SSLContext createSslContext( + final String keyStoreType, + final String keyStoreLocation, + final char[] keyStorePassword, + final String trustStoreType, + final String trustStoreLocation, + final char[] trustStorePassword) + throws Exception { + + // Create the `KeyManagerFactory` + final KeyStore keyStore = loadKeyStore(keyStoreType, keyStoreLocation, keyStorePassword); + final String keyAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); + final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyAlgorithm); + keyManagerFactory.init(keyStore, keyStorePassword); + + // Create the `TrustManagerFactory` + final KeyStore trustStore = loadKeyStore(trustStoreType, trustStoreLocation, trustStorePassword); + final String trustAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustAlgorithm); + trustManagerFactory.init(trustStore); + + // Create the `SSLContext` + final SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + return sslContext; + } + + private static KeyStore loadKeyStore(final String storeType, final String storeLocation, final char[] storePassword) + throws Exception { + final KeyStore keyStore = KeyStore.getInstance(storeType); + final ByteArrayInputStream storeByteStream = + new ByteArrayInputStream(Files.readAllBytes(Paths.get(storeLocation))); + keyStore.load(storeByteStream, storePassword); + return keyStore; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java index abea02c1d06..17217157ed4 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java @@ -25,11 +25,11 @@ import org.apache.logging.log4j.core.net.Protocol; import org.apache.logging.log4j.core.net.ssl.KeyStoreConfiguration; import org.apache.logging.log4j.core.net.ssl.SslConfiguration; +import org.apache.logging.log4j.core.net.ssl.SslKeyStoreConstants; import org.apache.logging.log4j.core.net.ssl.StoreConfigurationException; import org.apache.logging.log4j.core.net.ssl.TlsSyslogTestUtil; import org.apache.logging.log4j.core.net.ssl.TrustStoreConfiguration; import org.apache.logging.log4j.core.test.net.mock.MockSyslogServerFactory; -import org.apache.logging.log4j.core.test.net.ssl.TestConstants; import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogMessageFormat; import org.junit.jupiter.api.Test; @@ -78,12 +78,12 @@ public void sendStructuredMessagesOverTls() throws IOException, InterruptedExcep } private void initServerSocketFactory() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = - new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, TestConstants::KEYSTORE_PWD, null, null); - final TrustStoreConfiguration tsc = - new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants::TRUSTSTORE_PWD, null, null); + final KeyStoreConfiguration ksc = new KeyStoreConfiguration( + SslKeyStoreConstants.KEYSTORE_LOCATION, SslKeyStoreConstants::KEYSTORE_PWD, null, null); + final TrustStoreConfiguration tsc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, SslKeyStoreConstants::TRUSTSTORE_PWD, null, null); sslConfiguration = SslConfiguration.createSSLConfiguration(null, ksc, tsc); - serverSocketFactory = sslConfiguration.getSslServerSocketFactory(); + serverSocketFactory = sslConfiguration.getSslContext().getServerSocketFactory(); } private void initTlsTestEnvironment(final int numberOfMessages, final TlsSyslogMessageFormat messageFormat) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SocketAppenderReconnectTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SocketAppenderReconnectTest.java deleted file mode 100644 index 317f40b7ce1..00000000000 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SocketAppenderReconnectTest.java +++ /dev/null @@ -1,441 +0,0 @@ -/* - * 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.net; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.appender.AppenderLoggingException; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Configurator; -import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; -import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; -import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; -import org.apache.logging.log4j.core.net.TcpSocketManager.HostResolver; -import org.apache.logging.log4j.core.test.ListErrorHandler; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.test.junit.StatusLoggerLevel; -import org.apache.logging.log4j.test.junit.UsingStatusListener; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -/** - * Tests reconnection support of {@link org.apache.logging.log4j.core.appender.SocketAppender}. - */ -@StatusLoggerLevel("OFF") -@Tag("sleepy") -class SocketAppenderReconnectTest { - - private static final long DEFAULT_POLL_MILLIS = 1_000L; - private static final long DEFAULT_STOP_MILLIS = 5_000L; - private static final int EPHEMERAL_PORT = 0; - private static final Logger LOGGER = StatusLogger.getLogger(); - - /** - * Tests if failures are propagated when reconnection fails. - * - * @see LOG4J2-2829 - */ - @Test - @UsingStatusListener - void repeating_reconnect_failures_should_be_propagated() throws Exception { - final ListErrorHandler handler = new ListErrorHandler(); - try (final LineReadingTcpServer server = new LineReadingTcpServer()) { - - // Start the server. - server.start("Main", EPHEMERAL_PORT); - final int port = server.serverSocket.getLocalPort(); - - // Initialize the logger context. - final LoggerContext loggerContext = initContext(port, handler); - try { - - // Verify the initial working state. - verifyLoggingSuccess(server, handler); - - // Stop the server, and verify the logging failure. - server.close(); - verifyLoggingFailure(handler); - - // Start the server again, and verify the logging success. - server.start("Main", port); - verifyLoggingSuccess(server, handler); - - } - - // Shutdown the logger context. - finally { - assertTrue(loggerContext.stop(DEFAULT_STOP_MILLIS, TimeUnit.MILLISECONDS)); - } - } - } - - /** - * Tests if all the {@link InetSocketAddress}es returned by an {@link HostResolver} is used for fallback on reconnect attempts. - */ - @Test - @UsingStatusListener - void reconnect_should_fallback_when_there_are_multiple_resolved_hosts() throws Exception { - final ListErrorHandler handler = new ListErrorHandler(); - try (final LineReadingTcpServer primaryServer = new LineReadingTcpServer(); - final LineReadingTcpServer secondaryServer = new LineReadingTcpServer()) { - - // Start servers. - primaryServer.start("Primary", EPHEMERAL_PORT); - secondaryServer.start("Secondary", EPHEMERAL_PORT); - - // Mock the host resolver. - final FixedHostResolver hostResolver = FixedHostResolver.ofServers(primaryServer, secondaryServer); - TcpSocketManager.setHostResolver(hostResolver); - try { - - // Initialize the logger context. - final LoggerContext loggerContext = initContext( - // Passing an invalid port, since the resolution is supposed to be performed by the mocked host - // resolver anyway. - // Here, 0 does NOT mean an ephemeral port. - 0, handler); - try { - - // Verify the initial working state on the primary server. - verifyLoggingSuccess(primaryServer, handler); - - // Stop the primary server, and verify the logging success due to fallback on to the secondary - // server. - primaryServer.close(); - verifyLoggingSuccess(secondaryServer, handler); - - } - - // Shutdown the logger context. - finally { - assertTrue(loggerContext.stop(DEFAULT_STOP_MILLIS, TimeUnit.MILLISECONDS)); - } - - } finally { - // Reset the host resolver. - TcpSocketManager.setHostResolver(new HostResolver()); - } - } - } - - private static LoggerContext initContext(final int port, final ListErrorHandler handler) { - - // Create the configuration builder. - final ConfigurationBuilder configBuilder = - ConfigurationBuilderFactory.newConfigurationBuilder() - .setStatusLevel(Level.ERROR) - .setConfigurationName(SocketAppenderReconnectTest.class.getSimpleName()); - - // Create the configuration. - final String appenderName = "Socket"; - final Configuration config = configBuilder - .add(configBuilder - .newAppender(appenderName, "SOCKET") - .addAttribute("host", "localhost") - .addAttribute("port", String.valueOf(port)) - .addAttribute("protocol", Protocol.TCP) - .addAttribute("ignoreExceptions", false) - .addAttribute("reconnectionDelayMillis", 10) - .addAttribute("immediateFlush", true) - .add(configBuilder.newLayout("PatternLayout").addAttribute("pattern", "%m%n"))) - .add(configBuilder.newLogger("org.apache.logging.log4j", Level.DEBUG)) - .add(configBuilder.newRootLogger(Level.ERROR).add(configBuilder.newAppenderRef(appenderName))) - .setStatusLevel(Level.OFF) - .build(true); - - config.getAppender("Socket").setHandler(handler); - // Initialize the configuration. - return Configurator.initialize(config); - } - - private static void verifyLoggingSuccess(final LineReadingTcpServer server, final ListErrorHandler handler) - throws Exception { - final int messageCount = 100; - // noinspection ConstantConditions - assertTrue( - messageCount > 1, - "was expecting messageCount to be bigger than 1 due to LOG4J2-2829, found: " + messageCount); - final List expectedMessages = IntStream.range(0, messageCount) - .mapToObj(messageIndex -> String.format("m%02d", messageIndex)) - .collect(Collectors.toList()); - final Logger logger = LogManager.getLogger(); - // Due to socket initialization, the first write() might need some extra effort. - awaitUntilSucceeds(() -> logger.info(expectedMessages.get(0))); - handler.clear(); - for (int messageIndex = 1; messageIndex < expectedMessages.size(); messageIndex++) { - logger.info(expectedMessages.get(messageIndex)); - } - expectedMessages.forEach(logger::info); - final List actualMessages = server.pollLines(messageCount); - assertThat(actualMessages).containsExactlyElementsOf(expectedMessages); - assertThat(handler.getStatusData()).isEmpty(); - } - - private static void awaitUntilSucceeds(final Runnable runnable) { - // These figures are collected via trial-and-error; nothing scientific to look for here. - final long pollIntervalMillis = DEFAULT_POLL_MILLIS; - final long timeoutSeconds = 120L; - await().pollInterval(pollIntervalMillis, TimeUnit.MILLISECONDS) - .atMost(timeoutSeconds, TimeUnit.SECONDS) - .until(() -> { - runnable.run(); - return true; - }); - } - - private static void verifyLoggingFailure(final ListErrorHandler listener) { - final Logger logger = LogManager.getLogger(); - final int retryCount = 3; - // noinspection ConstantConditions - assertTrue( - retryCount > 1, - "was expecting retryCount to be bigger than 1 due to LOG4J2-2829, found: " + retryCount); - for (int i = 0; i < retryCount; i++) { - try { - logger.info("should fail #" + i); - fail("should have failed #" + i); - } catch (final AppenderLoggingException ignored) { - assertThat(listener.getStatusData()).hasSize(2 * (i + 1)); - } - } - listener.clear(); - } - - /** - * A simple TCP server implementation reading the accepted connection's input stream into a blocking queue of lines. - *

- * The implementation is thread-safe, yet connections are handled sequentially, i.e., no parallelization. - * The input stream of the connection is decoded in UTF-8. - *

- */ - private static final class LineReadingTcpServer implements AutoCloseable { - - private static final int UNBOUND_PORT = -1; - - private volatile boolean running; - - private ServerSocket serverSocket; - - private Socket clientSocket; - - private Thread readerThread; - - private final BlockingQueue lines = new LinkedBlockingQueue<>(); - - private LineReadingTcpServer() {} - - private synchronized void start(final String name, final int port) throws IOException { - if (!running) { - running = true; - serverSocket = createServerSocket(port); - readerThread = createReaderThread(name); - // Make sure the server socket is ready. - if (serverSocket.getLocalPort() == UNBOUND_PORT || !serverSocket.isBound()) { - try { - Thread.sleep(DEFAULT_POLL_MILLIS); - } catch (InterruptedException e) { - throw new IllegalStateException(e); - } - } - assertNotEquals( - UNBOUND_PORT, - serverSocket.getLocalPort(), - () -> String.format( - "Server socket is not bound to port %s (0 = ephemeral). This can only happen if a machine runs out of ports.", - port)); - } - } - - private ServerSocket createServerSocket(final int port) throws IOException { - final ServerSocket serverSocket = new ServerSocket(port); - serverSocket.setReuseAddress(true); - serverSocket.setSoTimeout(0); // Zero indicates accept() will block indefinitely. - return serverSocket; - } - - private Thread createReaderThread(final String name) { - final String threadName = "LineReadingTcpSocketServerReader-" + name; - final Thread thread = new Thread(this::acceptClients, threadName); - thread.setDaemon(true); // Avoid blocking JVM exit. - thread.setUncaughtExceptionHandler( - (ignored, error) -> LOGGER.error("uncaught reader thread exception", error)); - thread.start(); - return thread; - } - - private void acceptClients() { - try { - while (running) { - acceptClient(); - } - } catch (final Exception error) { - LOGGER.error("failed accepting client connections", error); - } - } - - private void acceptClient() throws Exception { - - // Accept the client connection. - final Socket clientSocket; - try { - clientSocket = serverSocket.accept(); - } catch (SocketException ignored) { - return; - } - clientSocket.setSoLinger(true, 0); // Enable immediate forceful close. - synchronized (this) { - if (running) { - this.clientSocket = clientSocket; - } - } - - // Read from the client. - try (final InputStream clientInputStream = clientSocket.getInputStream(); - final InputStreamReader clientReader = - new InputStreamReader(clientInputStream, StandardCharsets.UTF_8); - final BufferedReader clientBufferedReader = new BufferedReader(clientReader)) { - while (running) { - final String line = clientBufferedReader.readLine(); - if (line == null) { - break; - } - lines.put(line); - } - } - - // Ignore connection failures. - catch (final SocketException ignored) { - } - - // Clean up the client connection. - finally { - try { - synchronized (this) { - if (!clientSocket.isClosed()) { - clientSocket.shutdownOutput(); - clientSocket.close(); - } - this.clientSocket = null; - } - } catch (final Exception error) { - LOGGER.error("failed closing client socket", error); - } - } - } - - @Override - public void close() throws Exception { - - // Stop the reader, if running. - Thread stoppedReaderThread = null; - synchronized (this) { - if (running) { - running = false; - // acceptClient() might have closed the client socket due to a connection failure and haven't - // created a new one yet. - // Hence, here we double-check if the client connection is in place. - if (clientSocket != null && !clientSocket.isClosed()) { - // Interrupting a thread is not sufficient to unblock operations waiting on socket I/O: - // https://stackoverflow.com/a/4426050/1278899 - // Hence, here we close the client socket to unblock the read from the client socket. - clientSocket.close(); - } - serverSocket.close(); - stoppedReaderThread = readerThread; - clientSocket = null; - serverSocket = null; - readerThread = null; - } - } - - // We wait for the termination of the reader thread outside the synchronized block. - // Otherwise, there is a chance of deadlock with this join() and the synchronized block inside the - // acceptClient(). - if (stoppedReaderThread != null) { - stoppedReaderThread.join(); - } - } - - private List pollLines(@SuppressWarnings("SameParameterValue") final int count) - throws InterruptedException, TimeoutException { - final List polledLines = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - final String polledLine = pollLine(); - polledLines.add(polledLine); - } - return polledLines; - } - - private String pollLine() throws InterruptedException, TimeoutException { - final String line = lines.poll(2, TimeUnit.SECONDS); - if (line == null) { - throw new TimeoutException(); - } - return line; - } - } - - /** - * {@link HostResolver} implementation always resolving to the given list of {@link #addresses}. - */ - private static final class FixedHostResolver extends HostResolver { - - private final List addresses; - - private FixedHostResolver(final List addresses) { - this.addresses = addresses; - } - - private static FixedHostResolver ofServers(final LineReadingTcpServer... servers) { - final List addresses = Arrays.stream(servers) - .map(server -> (InetSocketAddress) server.serverSocket.getLocalSocketAddress()) - .collect(Collectors.toList()); - return new FixedHostResolver(addresses); - } - - @Override - public List resolveHost(final String ignoredHost, final int ignoredPort) { - return addresses; - } - } -} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.java index be3844df8ce..5413d267d30 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.java @@ -16,57 +16,98 @@ */ package org.apache.logging.log4j.core.net.ssl; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import java.security.KeyStore; -import org.apache.logging.log4j.core.test.net.ssl.TestConstants; -import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import java.util.Collections; +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junitpioneer.jupiter.SetSystemProperty; -@StatusLoggerLevel("OFF") +@UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure +@SetSystemProperty(key = "sun.security.mscapi.keyStoreCompatibilityMode", value = "false") public class KeyStoreConfigurationTest { + @SuppressWarnings("deprecation") @Test public void loadEmptyConfigurationDeprecated() { assertThrows( StoreConfigurationException.class, - () -> new KeyStoreConfiguration(null, TestConstants.NULL_PWD, null, null)); + () -> new KeyStoreConfiguration(null, SslKeyStoreConstants.NULL_PWD, null, null)); } @Test public void loadEmptyConfiguration() { assertThrows( StoreConfigurationException.class, - () -> new KeyStoreConfiguration(null, new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null)); + () -> new KeyStoreConfiguration( + null, new MemoryPasswordProvider(SslKeyStoreConstants.NULL_PWD), null, null)); } @Test public void loadNotEmptyConfigurationDeprecated() throws StoreConfigurationException { @SuppressWarnings("deprecation") final KeyStoreConfiguration ksc = new KeyStoreConfiguration( - TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), TestConstants.KEYSTORE_TYPE, null); + SslKeyStoreConstants.KEYSTORE_LOCATION, + SslKeyStoreConstants.KEYSTORE_PWD(), + SslKeyStoreConstants.KEYSTORE_TYPE, + null); final KeyStore ks = ksc.getKeyStore(); assertNotNull(ks); + checkKeystoreConfiguration(ksc); } - @Test - public void loadNotEmptyConfiguration() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration( - TestConstants.KEYSTORE_FILE, - new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), - TestConstants.KEYSTORE_TYPE, - null); + static Stream configurations() { + final Stream.Builder builder = Stream.builder(); + builder.add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_PWD, + SslKeyStoreConstants.KEYSTORE_TYPE)) + .add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_P12_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_P12_PWD, + SslKeyStoreConstants.KEYSTORE_P12_TYPE)) + .add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_P12_NOPASS_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_P12_NOPASS_PWD, + SslKeyStoreConstants.KEYSTORE_P12_NOPASS_TYPE)); + if (OS.WINDOWS.isCurrentOs()) { + builder.add(Arguments.of(null, (Supplier) () -> null, SslKeyStoreConstants.WINDOWS_KEYSTORE_TYPE)) + .add(Arguments.of( + null, (Supplier) () -> null, SslKeyStoreConstants.WINDOWS_TRUSTSTORE_TYPE)); + } + return builder.build(); + } + + @ParameterizedTest + @MethodSource("configurations") + public void loadNotEmptyConfiguration( + final String keystoreFile, final Supplier password, final String keystoreType) + throws StoreConfigurationException { + final KeyStoreConfiguration ksc = + new KeyStoreConfiguration(keystoreFile, new MemoryPasswordProvider(password.get()), keystoreType, null); final KeyStore ks = ksc.getKeyStore(); assertNotNull(ks); + checkKeystoreConfiguration(ksc); } @Test public void returnTheSameKeyStoreAfterMultipleLoadsDeprecated() throws StoreConfigurationException { @SuppressWarnings("deprecation") final KeyStoreConfiguration ksc = new KeyStoreConfiguration( - TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), TestConstants.KEYSTORE_TYPE, null); + SslKeyStoreConstants.KEYSTORE_LOCATION, + SslKeyStoreConstants.KEYSTORE_PWD(), + SslKeyStoreConstants.KEYSTORE_TYPE, + null); final KeyStore ks = ksc.getKeyStore(); final KeyStore ks2 = ksc.getKeyStore(); assertSame(ks, ks2); @@ -75,9 +116,9 @@ public void returnTheSameKeyStoreAfterMultipleLoadsDeprecated() throws StoreConf @Test public void returnTheSameKeyStoreAfterMultipleLoads() throws StoreConfigurationException { final KeyStoreConfiguration ksc = new KeyStoreConfiguration( - TestConstants.KEYSTORE_FILE, - new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), - TestConstants.KEYSTORE_TYPE, + SslKeyStoreConstants.KEYSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.KEYSTORE_PWD()), + SslKeyStoreConstants.KEYSTORE_TYPE, null); final KeyStore ks = ksc.getKeyStore(); final KeyStore ks2 = ksc.getKeyStore(); @@ -89,17 +130,57 @@ public void returnTheSameKeyStoreAfterMultipleLoads() throws StoreConfigurationE public void wrongPasswordDeprecated() { assertThrows( StoreConfigurationException.class, - () -> new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, "wrongPassword!", null, null)); + () -> new KeyStoreConfiguration(SslKeyStoreConstants.KEYSTORE_LOCATION, "wrongPassword!", null, null)); } - @Test - public void wrongPassword() { + static Stream wrongConfigurations() { + final Stream.Builder builder = Stream.builder(); + builder.add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_P12_NOPASS_PWD, + SslKeyStoreConstants.KEYSTORE_TYPE)) + .add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_LOCATION, + (Supplier) () -> "wrongPassword!".toCharArray(), + SslKeyStoreConstants.KEYSTORE_TYPE)) + .add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_P12_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_P12_NOPASS_PWD, + SslKeyStoreConstants.KEYSTORE_P12_TYPE)) + .add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_P12_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_P12_NOPASS_PWD, + SslKeyStoreConstants.KEYSTORE_P12_TYPE)); + if (OS.WINDOWS.isCurrentOs()) { + builder.add(Arguments.of( + null, (Supplier) () -> new char[0], SslKeyStoreConstants.WINDOWS_KEYSTORE_TYPE)) + .add(Arguments.of( + null, (Supplier) () -> new char[0], SslKeyStoreConstants.WINDOWS_TRUSTSTORE_TYPE)); + } + return builder.build(); + } + + @ParameterizedTest + @MethodSource("wrongConfigurations") + public void wrongPassword(final String keystoreFile, final Supplier password, final String keystoreType) { assertThrows( StoreConfigurationException.class, () -> new KeyStoreConfiguration( - TestConstants.KEYSTORE_FILE, - new MemoryPasswordProvider("wrongPassword!".toCharArray()), - null, - null)); + keystoreFile, new MemoryPasswordProvider(password.get()), keystoreType, null)); + } + + static void checkKeystoreConfiguration(final AbstractKeyStoreConfiguration config) { + // Not all keystores throw immediately if the password is wrong + assertDoesNotThrow(() -> { + final KeyStore ks = config.getKeyStore(); + for (final String alias : Collections.list(ks.aliases())) { + if (ks.isCertificateEntry(alias)) { + ks.getCertificate(alias); + } + if (ks.isKeyEntry(alias)) { + ks.getKey(alias, config.getPassword()); + } + } + }); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactoryTest.java index f6f6d2d1431..ad78727ec54 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactoryTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactoryTest.java @@ -19,57 +19,107 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.stream.Stream; import org.apache.logging.log4j.core.impl.CoreProperties.KeyManagerFactoryProperties; import org.apache.logging.log4j.core.impl.CoreProperties.KeyStoreProperties; import org.apache.logging.log4j.core.impl.CoreProperties.TransportSecurityProperties; -import org.apache.logging.log4j.core.test.net.ssl.TestConstants; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -public class SslConfigurationFactoryTest { +@UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure +class SslConfigurationFactoryTest { - private static KeyStoreProperties createKeyStoreProps() { - return new KeyStoreProperties( - new KeyManagerFactoryProperties(null), - TestConstants.KEYSTORE_FILE_RESOURCE, - null, - null, - null, - TestConstants.KEYSTORE_TYPE); - } + @Test + void testStaticConfiguration() { - private static KeyStoreProperties createTrustStoreProps() { - return new KeyStoreProperties( + // Case 1: Empty configuration + final TransportSecurityProperties transportSecurity = TransportSecurityProperties.defaultValue(); + SslConfiguration sslConfiguration = SslConfigurationFactory.getSslConfiguration(transportSecurity); + assertNull(sslConfiguration); + + // Case 2: Only key store + final KeyStoreProperties keyStore = new KeyStoreProperties( new KeyManagerFactoryProperties(null), - TestConstants.TRUSTSTORE_FILE_RESOURCE, + SslKeyStoreConstants.KEYSTORE_LOCATION, null, null, null, - TestConstants.KEYSTORE_TYPE); - } - - @Test - public void testStaticConfiguration() { - final KeyStoreProperties keyStore = createKeyStoreProps(); - final KeyStoreProperties trustStore = createTrustStoreProps(); - final TransportSecurityProperties transportSecurity = TransportSecurityProperties.defaultValue(); - // No keystore and truststore -> no SslConfiguration - SslConfiguration sslConfiguration = SslConfigurationFactory.getSslConfiguration(transportSecurity); - assertNull(sslConfiguration); - // Only keystore + SslKeyStoreConstants.KEYSTORE_TYPE); sslConfiguration = SslConfigurationFactory.getSslConfiguration(transportSecurity.withKeyStore(keyStore)); assertNotNull(sslConfiguration); assertNotNull(sslConfiguration.getKeyStoreConfig()); assertNull(sslConfiguration.getTrustStoreConfig()); - // Only truststore + + // Case 3: Only trust store + final KeyStoreProperties trustStore = new KeyStoreProperties( + new KeyManagerFactoryProperties(null), + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + null, + null, + null, + SslKeyStoreConstants.TRUSTSTORE_TYPE); sslConfiguration = SslConfigurationFactory.getSslConfiguration(transportSecurity.withTrustStore(trustStore)); assertNotNull(sslConfiguration); assertNull(sslConfiguration.getKeyStoreConfig()); assertNotNull(sslConfiguration.getTrustStoreConfig()); - // Both + + // Case 4: Both key and trust stores sslConfiguration = SslConfigurationFactory.getSslConfiguration( transportSecurity.withKeyStore(keyStore).withTrustStore(trustStore)); assertNotNull(sslConfiguration); assertNotNull(sslConfiguration.getKeyStoreConfig()); assertNotNull(sslConfiguration.getTrustStoreConfig()); } + + static Stream windowsKeystoreConfigs() { + final String[] emptyOrNull = {"", null}; + final Stream.Builder builder = Stream.builder(); + for (final String location : emptyOrNull) { + for (final String password : emptyOrNull) { + builder.add(Arguments.of(location, password)); + } + } + return builder.build(); + } + + @EnabledOnOs(OS.WINDOWS) + @ParameterizedTest + @MethodSource("windowsKeystoreConfigs") + public void testPasswordLessStores(String location, String password) { + + // Create the configuration + final char[] passwordChars = password != null ? password.toCharArray() : null; + final KeyStoreProperties keyStore = new KeyStoreProperties( + new KeyManagerFactoryProperties(null), + location, + passwordChars, + null, + null, + SslKeyStoreConstants.WINDOWS_KEYSTORE_TYPE); + final KeyStoreProperties trustStore = new KeyStoreProperties( + new KeyManagerFactoryProperties(null), + location, + passwordChars, + null, + null, + SslKeyStoreConstants.WINDOWS_TRUSTSTORE_TYPE); + final TransportSecurityProperties transportSecurity = TransportSecurityProperties.defaultValue() + .withKeyStore(keyStore) + .withTrustStore(trustStore); + + // Verify the configuration + final SslConfiguration config = SslConfigurationFactory.getSslConfiguration(transportSecurity); + assertNotNull(config); + final KeyStoreConfiguration keyStoreConfig = config.getKeyStoreConfig(); + assertNotNull(keyStoreConfig); + KeyStoreConfigurationTest.checkKeystoreConfiguration(keyStoreConfig); + final TrustStoreConfiguration trustStoreConfig = config.getTrustStoreConfig(); + assertNotNull(trustStoreConfig); + KeyStoreConfigurationTest.checkKeystoreConfiguration(trustStoreConfig); + } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java index 4c404704514..82673f9a9db 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java @@ -26,11 +26,9 @@ import java.net.UnknownHostException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; -import org.apache.logging.log4j.core.test.net.ssl.TestConstants; -import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.junit.jupiter.api.Test; -@StatusLoggerLevel("OFF") class SslConfigurationTest { private static final String TLS_TEST_HOST = "apache.org"; @@ -38,13 +36,13 @@ class SslConfigurationTest { private static SslConfiguration createTestSslConfigurationResources() throws StoreConfigurationException { final KeyStoreConfiguration ksc = new KeyStoreConfiguration( - TestConstants.KEYSTORE_FILE_RESOURCE, - new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), - TestConstants.KEYSTORE_TYPE, + SslKeyStoreConstants.KEYSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.KEYSTORE_PWD()), + SslKeyStoreConstants.KEYSTORE_TYPE, null); final TrustStoreConfiguration tsc = new TrustStoreConfiguration( - TestConstants.TRUSTSTORE_FILE_RESOURCE, - new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.TRUSTSTORE_PWD()), null, null); return SslConfiguration.createSSLConfiguration(null, ksc, tsc); @@ -52,12 +50,15 @@ private static SslConfiguration createTestSslConfigurationResources() throws Sto private static SslConfiguration createTestSslConfigurationFiles() throws StoreConfigurationException { final KeyStoreConfiguration ksc = new KeyStoreConfiguration( - TestConstants.KEYSTORE_FILE, - new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), - TestConstants.KEYSTORE_TYPE, + SslKeyStoreConstants.KEYSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.KEYSTORE_PWD()), + SslKeyStoreConstants.KEYSTORE_TYPE, null); final TrustStoreConfiguration tsc = new TrustStoreConfiguration( - TestConstants.TRUSTSTORE_FILE, new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.TRUSTSTORE_PWD()), + SslKeyStoreConstants.TRUSTSTORE_TYPE, + null); return SslConfiguration.createSSLConfiguration(null, ksc, tsc); } @@ -66,7 +67,7 @@ void testGettersFromScratchFiles() throws StoreConfigurationException { assertNotNull(createTestSslConfigurationFiles().getProtocol()); assertNotNull(createTestSslConfigurationFiles().getKeyStoreConfig()); assertNotNull(createTestSslConfigurationFiles().getSslContext()); - assertNotNull(createTestSslConfigurationFiles().getSslSocketFactory()); + assertNotNull(createTestSslConfigurationFiles().getSslContext().getSocketFactory()); assertNotNull(createTestSslConfigurationFiles().getTrustStoreConfig()); } @@ -75,7 +76,7 @@ void testGettersFromScratchResources() throws StoreConfigurationException { assertNotNull(createTestSslConfigurationResources().getProtocol()); assertNotNull(createTestSslConfigurationResources().getKeyStoreConfig()); assertNotNull(createTestSslConfigurationResources().getSslContext()); - assertNotNull(createTestSslConfigurationResources().getSslSocketFactory()); + assertNotNull(createTestSslConfigurationResources().getSslContext().getSocketFactory()); assertNotNull(createTestSslConfigurationResources().getTrustStoreConfig()); } @@ -89,14 +90,14 @@ void testEquals() { @Test void emptyConfigurationDoesNotCauseNullSSLSocketFactory() { final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, null); - final SSLSocketFactory factory = sc.getSslSocketFactory(); + final SSLSocketFactory factory = sc.getSslContext().getSocketFactory(); assertNotNull(factory); } @Test void emptyConfigurationHasDefaultTrustStore() throws IOException { final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, null); - final SSLSocketFactory factory = sc.getSslSocketFactory(); + final SSLSocketFactory factory = sc.getSslContext().getSocketFactory(); try { try (final SSLSocket clientSocket = (SSLSocket) factory.createSocket(TLS_TEST_HOST, TLS_TEST_PORT)) { assertNotNull(clientSocket); @@ -110,9 +111,12 @@ void emptyConfigurationHasDefaultTrustStore() throws IOException { @Test void connectionFailsWithoutValidServerCertificate() throws IOException, StoreConfigurationException { final TrustStoreConfiguration tsc = new TrustStoreConfiguration( - TestConstants.TRUSTSTORE_FILE, new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null); + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.NULL_PWD), + null, + null); final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, tsc); - final SSLSocketFactory factory = sc.getSslSocketFactory(); + final SSLSocketFactory factory = sc.getSslContext().getSocketFactory(); try { try (final SSLSocket clientSocket = (SSLSocket) factory.createSocket(TLS_TEST_HOST, TLS_TEST_PORT)) { try (final OutputStream os = clientSocket.getOutputStream()) { @@ -126,11 +130,15 @@ void connectionFailsWithoutValidServerCertificate() throws IOException, StoreCon } @Test + @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure void loadKeyStoreWithoutPassword() throws StoreConfigurationException { final KeyStoreConfiguration ksc = new KeyStoreConfiguration( - TestConstants.KEYSTORE_FILE, new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null); + SslKeyStoreConstants.KEYSTORE_P12_NOPASS_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.NULL_PWD), + SslKeyStoreConstants.KEYSTORE_P12_NOPASS_TYPE, + null); final SslConfiguration sslConf = SslConfiguration.createSSLConfiguration(null, ksc, null); - final SSLSocketFactory factory = sslConf.getSslSocketFactory(); + final SSLSocketFactory factory = sslConf.getSslContext().getSocketFactory(); assertNotNull(factory); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslKeyStoreConstants.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslKeyStoreConstants.java new file mode 100644 index 00000000000..387b0f46bd3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslKeyStoreConstants.java @@ -0,0 +1,90 @@ +/* + * 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.net.ssl; + +public final class SslKeyStoreConstants { + + private static final String PATH = "src/test/resources/org/apache/logging/log4j/core/net/ssl/"; + + /// Trust store (JKS) ///////////////////////////////////////////////////// + + public static final String TRUSTSTORE_LOCATION = PATH + "trustStore.jks"; + + public static char[] TRUSTSTORE_PWD() { + return "aTrustStoreSecret".toCharArray(); + } + + public static final String TRUSTSTORE_TYPE = "JKS"; + + /// Trust store #2 (JKS) ////////////////////////////////////////////////// + + public static final String TRUSTSTORE2_LOCATION = PATH + "trustStore2.jks"; + + public static char[] TRUSTSTORE2_PWD() { + return "aTrustStoreSecret2".toCharArray(); + } + + public static final String TRUSTSTORE2_TYPE = "JKS"; + + /// Key store (JKS) /////////////////////////////////////////////////////// + + public static final String KEYSTORE_LOCATION = PATH + "keyStore.jks"; + + public static char[] KEYSTORE_PWD() { + return "aKeyStoreSecret".toCharArray(); + } + + public static final String KEYSTORE_TYPE = "JKS"; + + /// Key store #2 (JKS) //////////////////////////////////////////////////// + + public static final String KEYSTORE2_LOCATION = PATH + "keyStore2.jks"; + + public static char[] KEYSTORE2_PWD() { + return "aKeyStoreSecret2".toCharArray(); + } + + public static final String KEYSTORE2_TYPE = "JKS"; + + /// Key store (P12) /////////////////////////////////////////////////////// + + public static final String KEYSTORE_P12_LOCATION = PATH + "keyStore.p12"; + + public static char[] KEYSTORE_P12_PWD() { + return "aKeyStoreSecret".toCharArray(); + } + + public static final String KEYSTORE_P12_TYPE = "PKCS12"; + + /// Key store (P12 without password) ////////////////////////////////////// + + public static final String KEYSTORE_P12_NOPASS_LOCATION = PATH + "keyStore-nopass.p12"; + + public static char[] KEYSTORE_P12_NOPASS_PWD() { + return new char[0]; + } + + public static final String KEYSTORE_P12_NOPASS_TYPE = "PKCS12"; + + /// Other ///////////////////////////////////////////////////////////////// + + public static final char[] NULL_PWD = null; + + public static final String WINDOWS_KEYSTORE_TYPE = "Windows-MY"; + + public static final String WINDOWS_TRUSTSTORE_TYPE = "Windows-ROOT"; +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.java index 327e2f79001..e23be4d1f0c 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.java @@ -21,18 +21,17 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.security.KeyStore; -import org.apache.logging.log4j.core.test.net.ssl.TestConstants; -import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.junit.jupiter.api.Test; -@StatusLoggerLevel("OFF") +@UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure public class TrustStoreConfigurationTest { @SuppressWarnings("deprecation") @Test public void loadEmptyConfigurationDeprecated() { assertThrows( StoreConfigurationException.class, - () -> new TrustStoreConfiguration(null, TestConstants.NULL_PWD, null, null)); + () -> new TrustStoreConfiguration(null, SslKeyStoreConstants.NULL_PWD, null, null)); } @Test @@ -40,14 +39,14 @@ public void loadEmptyConfiguration() { assertThrows( StoreConfigurationException.class, () -> new TrustStoreConfiguration( - null, new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null)); + null, new MemoryPasswordProvider(SslKeyStoreConstants.NULL_PWD), null, null)); } @Test public void loadConfigurationDeprecated() throws StoreConfigurationException { @SuppressWarnings("deprecation") - final TrustStoreConfiguration ksc = - new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), null, null); + final TrustStoreConfiguration ksc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, SslKeyStoreConstants.TRUSTSTORE_PWD(), null, null); final KeyStore ks = ksc.getKeyStore(); assertNotNull(ks); } @@ -55,7 +54,10 @@ public void loadConfigurationDeprecated() throws StoreConfigurationException { @Test public void loadConfiguration() throws StoreConfigurationException { final TrustStoreConfiguration ksc = new TrustStoreConfiguration( - TestConstants.TRUSTSTORE_FILE, new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.TRUSTSTORE_PWD()), + null, + null); final KeyStore ks = ksc.getKeyStore(); assertNotNull(ks); } @@ -63,8 +65,8 @@ public void loadConfiguration() throws StoreConfigurationException { @Test public void returnTheSameKeyStoreAfterMultipleLoadsDeprecated() throws StoreConfigurationException { @SuppressWarnings("deprecation") - final TrustStoreConfiguration ksc = - new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), null, null); + final TrustStoreConfiguration ksc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, SslKeyStoreConstants.TRUSTSTORE_PWD(), null, null); final KeyStore ks = ksc.getKeyStore(); final KeyStore ks2 = ksc.getKeyStore(); assertSame(ks, ks2); @@ -73,7 +75,10 @@ public void returnTheSameKeyStoreAfterMultipleLoadsDeprecated() throws StoreConf @Test public void returnTheSameKeyStoreAfterMultipleLoads() throws StoreConfigurationException { final TrustStoreConfiguration ksc = new TrustStoreConfiguration( - TestConstants.TRUSTSTORE_FILE, new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.TRUSTSTORE_PWD()), + null, + null); final KeyStore ks = ksc.getKeyStore(); final KeyStore ks2 = ksc.getKeyStore(); assertSame(ks, ks2); @@ -85,7 +90,7 @@ public void wrongPasswordDeprecated() { assertThrows( StoreConfigurationException.class, () -> new TrustStoreConfiguration( - TestConstants.TRUSTSTORE_FILE, "wrongPassword!".toCharArray(), null, null)); + SslKeyStoreConstants.TRUSTSTORE_LOCATION, "wrongPassword!".toCharArray(), null, null)); } @Test @@ -93,7 +98,7 @@ public void wrongPassword() { assertThrows( StoreConfigurationException.class, () -> new TrustStoreConfiguration( - TestConstants.TRUSTSTORE_FILE, + SslKeyStoreConstants.TRUSTSTORE_LOCATION, new MemoryPasswordProvider("wrongPassword!".toCharArray()), null, null)); diff --git a/log4j-core-test/src/test/resources/log4j-ssl-socket-options.xml b/log4j-core-test/src/test/resources/log4j-ssl-socket-options.xml deleted file mode 100644 index 26a3fa8a89d..00000000000 --- a/log4j-core-test/src/test/resources/log4j-ssl-socket-options.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/.gitignore b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/.gitignore new file mode 100644 index 00000000000..3fec32c8427 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/README b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/README deleted file mode 100644 index 11aa30a3e22..00000000000 --- a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/README +++ /dev/null @@ -1,13 +0,0 @@ -This directory contains files to test the TLSSyslogAppender and its helpers classes. Please don't remove them. - -All certificates are signed with log4j2-cacert.pem, which is the root certificate (you can find help here to install it: -http://askubuntu.com/questions/73287/how-do-i-install-a-root-certificate). The client.log4j2-keystore.jks contains the -client private key, the corresponding certificate (client.log4j2-crt.pem) and the root CA certificate -(log4j2-cacert.pem). The root CA certificate is also imported into the truststore.jks. - -The server.log4j2-key.pem is a private key generated by OpenSSL, which can be used as a server private key. The -server.log4j2-crt.pem is the corresponding certificate signed with log4j2-cacert.pem. - -All JKS files are created with password "changeit". - -The syslog-ng-sample.conf file contains the relevant part of a sample syslog-ng configuration with TLS support. diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/gencerts.sh b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/gencerts.sh deleted file mode 100755 index e970f1c5c91..00000000000 --- a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/gencerts.sh +++ /dev/null @@ -1,28 +0,0 @@ -mkdir tmp -# Create the CA key and certificate -openssl req -config rootca.conf -new -x509 -nodes -keyout tmp/log4j2-cacert.key -out tmp/log4j2-ca.crt -days 7302 -# Create the trust store and import the certificate -keytool -keystore ../truststore.jks -storetype jks -importcert -file 'tmp/log4j2-ca.crt' -keypass changeit -storepass changeit -alias log4j2-cacert -noprompt -#Import the root certificate -keytool -keystore ../client.log4j2-keystore.jks -alias log4j2-ca -importcert -file tmp/log4j2-ca.crt -keypass changeit -storepass changeit -noprompt -# Create the client private key in the client key store -keytool -genkeypair -keyalg RSA -alias client -keystore ../client.log4j2-keystore.jks -storepass changeit -keypass changeit -validity 7302 -keysize 2048 -dname "CN=client.log4j2, C=US" -# Create a signing request for the client # -keytool -keystore ../client.log4j2-keystore.jks -alias client -certreq -file tmp/client.csr -keypass changeit -storepass changeit -# Sign the client certificate -openssl x509 -req -CA 'tmp/log4j2-ca.crt' -CAkey 'tmp/log4j2-cacert.key' -in tmp/client.csr -out tmp/client.crt_signed -days 7302 -CAcreateserial -passin pass:changeit -# Verify the signed certificate -openssl verify -CAfile 'tmp/log4j2-ca.crt' tmp/client.crt_signed -#Import the client's signed certificate -keytool -keystore ../client.log4j2-keystore.jks -alias client -importcert -file tmp/client.crt_signed -keypass changeit -storepass changeit -noprompt -#Verify the keystore -keytool -list -keystore ../client.log4j2-keystore.jks -storepass changeit -# Create the server private key in the server key store -keytool -genkeypair -keyalg RSA -alias server -keystore tmp/server.log4j2-keystore.p12 -storepass changeit -storetype PKCS12 -keypass changeit -validity 7302 -keysize 2048 -dname "CN=server.log4j2, C=US" -# Create a signing request for the server # -keytool -keystore tmp/server.log4j2-keystore.p12 -alias server -certreq -file tmp/server.csr -keypass changeit -storepass changeit -# Sign the server certificate -openssl x509 -req -CA 'tmp/log4j2-ca.crt' -CAkey 'tmp/log4j2-cacert.key' -in tmp/server.csr -out ../server.log4j2-crt.pem -days 7302 -CAcreateserial -passin pass:changeit -# Extract the private key -openssl pkcs12 -in tmp/server.log4j2-keystore.p12 -passin pass:changeit -nokeys -out ../server.log4j2.pem -rm -rf tmp diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/rootca.conf b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/rootca.conf deleted file mode 100644 index 7d264462de2..00000000000 --- a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/rootca.conf +++ /dev/null @@ -1,9 +0,0 @@ -[ req ] -distinguished_name = CA_DN -prompt = no -output_password = changeit -default_bits = 2048 - -[ CA_DN ] -C = US -CN = log4j2-ca diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/server.conf b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/server.conf deleted file mode 100644 index 00fa79b85f9..00000000000 --- a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/server.conf +++ /dev/null @@ -1,9 +0,0 @@ -[ req ] -distinguished_name = CA_DN -prompt = no -output_password = changeit -default_bits = 2048 - -[ CA_DN ] -C = US -CN = iserver.log4j2 diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/client.log4j2-keystore.jks b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/client.log4j2-keystore.jks deleted file mode 100644 index 80892ba8899..00000000000 Binary files a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/client.log4j2-keystore.jks and /dev/null differ diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/generate-stores.sh b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/generate-stores.sh new file mode 100755 index 00000000000..df194cabe5a --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/generate-stores.sh @@ -0,0 +1,129 @@ +#!/bin/bash -eu +# 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. + +# Switch to the script directory +cd -- "$(dirname -- "${BASH_SOURCE[0]}")" + +# Reset and switch to the `tmp` +rm -rf tmp +mkdir tmp +cd tmp + +# Constants +caPassword="aCaSecret" +keySize=2048 +validDays=$[365 * 10] + +### CA key and certificate #################################################### + +# Generate the CA configuration +cat >ca.cfg < { + final Enumeration aliases; + try { + aliases = keyStoreConfig.getKeyStore().aliases(); + } catch (final KeyStoreException error) { + LOGGER.error( + "Failed reading the aliases for the key store located at `{}`", + keyStoreConfig.getLocation(), + error); + return Stream.empty(); + } + return Collections.list(aliases).stream().sorted().flatMap(alias -> { + final X509Certificate certificate; + try { + certificate = (X509Certificate) + keyStoreConfig.getKeyStore().getCertificate(alias); + } catch (final KeyStoreException error) { + LOGGER.error( + "Failed reading the certificate of alias `{}` for the key store located at `{}`", + alias, + keyStoreConfig.getLocation(), + error); + return Stream.empty(); + } + final String issuer = + certificate.getIssuerX500Principal().getName(); + final String serialNumber = + certificate.getSerialNumber().toString(); + return Stream.of(issuer, serialNumber); + }); + }) + .collect(Collectors.toList()) + .hashCode()); + } + @Override protected Socket createSocket(final InetSocketAddress socketAddress) throws IOException { final SSLSocketFactory socketFactory = createSslSocketFactory(sslConfig); @@ -162,7 +220,7 @@ private static SSLSocketFactory createSslSocketFactory(final SslConfiguration ss final SSLSocketFactory socketFactory; if (sslConf != null) { - socketFactory = sslConf.getSslSocketFactory(); + socketFactory = sslConf.getSslContext().getSocketFactory(); } else { socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java index 7de297dbaf3..8026372516d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java @@ -113,7 +113,8 @@ public static T createConnection( httpURLConnection.setIfModifiedSince(lastModifiedMillis); } if (url.getProtocol().equals(HTTPS) && sslConfiguration != null) { - ((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(sslConfiguration.getSslSocketFactory()); + ((HttpsURLConnection) httpURLConnection) + .setSSLSocketFactory(sslConfiguration.getSslContext().getSocketFactory()); if (!sslConfiguration.isVerifyHostName()) { ((HttpsURLConnection) httpURLConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java index edf92c1bbd9..021ec082d8b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java @@ -24,6 +24,7 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.util.Arrays; +import java.util.Objects; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.util.NetUtils; @@ -31,37 +32,35 @@ * Configuration of the KeyStore */ public class AbstractKeyStoreConfiguration extends StoreConfiguration { - static final char[] DEFAULT_PASSWORD = "changeit".toCharArray(); - private final KeyStore keyStore; private final String keyStoreType; + private final KeyStore keyStore; + public AbstractKeyStoreConfiguration( final String location, final PasswordProvider passwordProvider, final String keyStoreType) throws StoreConfigurationException { super(location, passwordProvider); this.keyStoreType = keyStoreType == null ? SslConfigurationDefaults.KEYSTORE_TYPE : keyStoreType; - this.keyStore = this.load(); + this.keyStore = load(); } - @Override - protected KeyStore load() throws StoreConfigurationException { + private KeyStore load() throws StoreConfigurationException { final String loadLocation = this.getLocation(); + final char[] password = this.getPassword(); LOGGER.debug("Loading keystore from location {}", loadLocation); try { + final KeyStore ks = KeyStore.getInstance(this.keyStoreType); if (loadLocation == null) { - throw new IOException("The location is null"); + if (keyStoreType.equalsIgnoreCase("JKS") || keyStoreType.equalsIgnoreCase("PKCS12")) { + throw new IOException("The location is null"); + } + ks.load(null, password); + LOGGER.debug("KeyStore successfully loaded"); + return ks; } try (final InputStream fin = openInputStream(loadLocation)) { - final KeyStore ks = KeyStore.getInstance(this.keyStoreType); - final char[] password = this.getPassword(); - try { - ks.load(fin, password != null ? password : DEFAULT_PASSWORD); - } finally { - if (password != null) { - Arrays.fill(password, '\0'); - } - } + ks.load(fin, password); LOGGER.debug("KeyStore successfully loaded from location {}", loadLocation); return ks; } @@ -90,10 +89,14 @@ protected KeyStore load() throws StoreConfigurationException { loadLocation, e); throw new StoreConfigurationException(loadLocation, e); + } finally { + if (password != null) { + Arrays.fill(password, '\0'); + } } } - private InputStream openInputStream(final String filePathOrUri) { + private static InputStream openInputStream(final String filePathOrUri) { return ConfigurationSource.fromUri(NetUtils.toURI(filePathOrUri)).getInputStream(); } @@ -105,7 +108,6 @@ public KeyStore getKeyStore() { public int hashCode() { final int prime = 31; int result = super.hashCode(); - result = prime * result + ((keyStore == null) ? 0 : keyStore.hashCode()); result = prime * result + ((keyStoreType == null) ? 0 : keyStoreType.hashCode()); return result; } @@ -122,18 +124,7 @@ public boolean equals(final Object obj) { return false; } final AbstractKeyStoreConfiguration other = (AbstractKeyStoreConfiguration) obj; - if (keyStore == null) { - if (other.keyStore != null) { - return false; - } - } else if (!keyStore.equals(other.keyStore)) { - return false; - } - if (keyStoreType == null) { - if (other.keyStoreType != null) { - return false; - } - } else if (!keyStoreType.equals(other.keyStoreType)) { + if (!Objects.equals(keyStoreType, other.keyStoreType)) { return false; } return true; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java index f99b4f2bfa3..5a542c39280 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java @@ -17,9 +17,6 @@ package org.apache.logging.log4j.core.net.ssl; import java.nio.file.Path; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; import java.util.Arrays; import javax.net.ssl.KeyManagerFactory; import org.apache.logging.log4j.core.impl.CoreProperties; @@ -149,20 +146,6 @@ public static KeyStoreConfiguration createKeyStoreConfiguration( } } - public KeyManagerFactory initKeyManagerFactory() - throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { - final KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(this.keyManagerFactoryAlgorithm); - final char[] password = this.getPassword(); - try { - kmFactory.init(this.getKeyStore(), password != null ? password : DEFAULT_PASSWORD); - } finally { - if (password != null) { - Arrays.fill(password, '\0'); - } - } - return kmFactory; - } - @Override public int hashCode() { final int prime = 31; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/PasswordProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/PasswordProvider.java index ca61176a309..ec37de3b9ec 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/PasswordProvider.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/PasswordProvider.java @@ -26,6 +26,7 @@ * is no longer needed. *

*/ +@FunctionalInterface public interface PasswordProvider { /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java index 9123e8068d8..496ad9144d5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java @@ -16,15 +16,11 @@ */ package org.apache.logging.log4j.core.net.ssl; -import java.security.KeyManagementException; -import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; +import java.util.Objects; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLServerSocketFactory; -import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import org.apache.logging.log4j.plugins.Configurable; @@ -33,30 +29,43 @@ import org.apache.logging.log4j.plugins.PluginElement; import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * SSL Configuration */ +@NullMarked @Configurable(printObject = true) @Plugin("Ssl") public class SslConfiguration { + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + + private final String protocol; + + private final boolean verifyHostName; + + @Nullable private final KeyStoreConfiguration keyStoreConfig; + + @Nullable private final TrustStoreConfiguration trustStoreConfig; + private final SSLContext sslContext; - private final String protocol; - private final boolean verifyHostName; private SslConfiguration( - final String protocol, - final KeyStoreConfiguration keyStoreConfig, - final TrustStoreConfiguration trustStoreConfig, - final boolean verifyHostName) { + @Nullable final String protocol, + final boolean verifyHostName, + @Nullable final KeyStoreConfiguration keyStoreConfig, + @Nullable final TrustStoreConfiguration trustStoreConfig) { this.keyStoreConfig = keyStoreConfig; this.trustStoreConfig = trustStoreConfig; - this.protocol = protocol == null ? SslConfigurationDefaults.PROTOCOL : protocol; - this.sslContext = this.createSslContext(); + final String effectiveProtocol = protocol == null ? SslConfigurationDefaults.PROTOCOL : protocol; + this.protocol = effectiveProtocol; this.verifyHostName = verifyHostName; + this.sslContext = createSslContext(effectiveProtocol, keyStoreConfig, trustStoreConfig); } /** @@ -71,157 +80,74 @@ public void clearSecrets() { } } - public SSLSocketFactory getSslSocketFactory() { - return sslContext.getSocketFactory(); - } - - public SSLServerSocketFactory getSslServerSocketFactory() { - return sslContext.getServerSocketFactory(); - } - - private SSLContext createSslContext() { - SSLContext context = null; - - try { - context = createSslContextBasedOnConfiguration(); - LOGGER.debug("Creating SSLContext with the given parameters"); - } catch (final TrustStoreConfigurationException e) { - context = createSslContextWithTrustStoreFailure(); - } catch (final KeyStoreConfigurationException e) { - context = createSslContextWithKeyStoreFailure(); - } - return context; - } - - private SSLContext createSslContextWithTrustStoreFailure() { - SSLContext context; - - try { - context = createSslContextWithDefaultTrustManagerFactory(); - LOGGER.debug("Creating SSLContext with default truststore"); - } catch (final KeyStoreConfigurationException e) { - context = createDefaultSslContext(); - LOGGER.debug("Creating SSLContext with default configuration"); - } - return context; - } - - private SSLContext createSslContextWithKeyStoreFailure() { - SSLContext context; - - try { - context = createSslContextWithDefaultKeyManagerFactory(); - LOGGER.debug("Creating SSLContext with default keystore"); - } catch (final TrustStoreConfigurationException e) { - context = createDefaultSslContext(); - LOGGER.debug("Creating SSLContext with default configuration"); - } - return context; - } - - private SSLContext createSslContextBasedOnConfiguration() - throws KeyStoreConfigurationException, TrustStoreConfigurationException { - return createSslContext(false, false); - } - - private SSLContext createSslContextWithDefaultKeyManagerFactory() throws TrustStoreConfigurationException { - try { - return createSslContext(true, false); - } catch (final KeyStoreConfigurationException dummy) { - LOGGER.debug("Exception occurred while using default keystore. This should be a BUG"); - return null; - } - } - - private SSLContext createSslContextWithDefaultTrustManagerFactory() throws KeyStoreConfigurationException { - try { - return createSslContext(false, true); - } catch (final TrustStoreConfigurationException dummy) { - LOGGER.debug("Exception occurred while using default truststore. This should be a BUG"); - return null; - } - } - - private SSLContext createDefaultSslContext() { + private static SSLContext createDefaultSslContext(final String protocol) { try { return SSLContext.getDefault(); - } catch (final NoSuchAlgorithmException e) { - LOGGER.error("Failed to create an SSLContext with default configuration", e); - return null; + } catch (final NoSuchAlgorithmException defaultContextError) { + LOGGER.error( + "Failed to create an `SSLContext` using the default configuration, falling back to creating an empty one", + defaultContextError); + try { + final SSLContext emptyContext = SSLContext.getInstance(protocol); + emptyContext.init(new KeyManager[0], new TrustManager[0], null); + return emptyContext; + } catch (final Exception emptyContextError) { + LOGGER.error("Failed to create an empty `SSLContext`", emptyContextError); + return null; + } } } - private SSLContext createSslContext( - final boolean loadDefaultKeyManagerFactory, final boolean loadDefaultTrustManagerFactory) - throws KeyStoreConfigurationException, TrustStoreConfigurationException { + private static SSLContext createSslContext( + final String protocol, + @Nullable final KeyStoreConfiguration keyStoreConfig, + @Nullable final TrustStoreConfiguration trustStoreConfig) { try { - KeyManager[] kManagers = null; - TrustManager[] tManagers = null; - - final SSLContext newSslContext = SSLContext.getInstance(this.protocol); - if (!loadDefaultKeyManagerFactory) { - final KeyManagerFactory kmFactory = loadKeyManagerFactory(); - kManagers = kmFactory.getKeyManagers(); - } - if (!loadDefaultTrustManagerFactory) { - final TrustManagerFactory tmFactory = loadTrustManagerFactory(); - tManagers = tmFactory.getTrustManagers(); - } - - newSslContext.init(kManagers, tManagers, null); - return newSslContext; - } catch (final NoSuchAlgorithmException e) { - LOGGER.error("No Provider supports a TrustManagerFactorySpi implementation for the specified protocol", e); - throw new TrustStoreConfigurationException(e); - } catch (final KeyManagementException e) { - LOGGER.error("Failed to initialize the SSLContext", e); - throw new KeyStoreConfigurationException(e); + final SSLContext sslContext = SSLContext.getInstance(protocol); + final KeyManager[] keyManagers = loadKeyManagers(keyStoreConfig); + final TrustManager[] trustManagers = loadTrustManagers(trustStoreConfig); + sslContext.init(keyManagers, trustManagers, null); + return sslContext; + } catch (final Exception error) { + LOGGER.error( + "Failed to create an `SSLContext` using the provided configuration, falling back to a default instance", + error); + return createDefaultSslContext(protocol); } } - private TrustManagerFactory loadTrustManagerFactory() throws TrustStoreConfigurationException { - if (trustStoreConfig == null) { - throw new TrustStoreConfigurationException(new Exception("The trustStoreConfiguration is null")); + private static KeyManager[] loadKeyManagers(@Nullable final KeyStoreConfiguration config) throws Exception { + if (config == null) { + return new KeyManager[0]; } - + final KeyManagerFactory factory = KeyManagerFactory.getInstance(config.getKeyManagerFactoryAlgorithm()); + final char[] password = config.getPassword(); try { - return trustStoreConfig.initTrustManagerFactory(); - } catch (final NoSuchAlgorithmException e) { - LOGGER.error("The specified algorithm is not available from the specified provider", e); - throw new TrustStoreConfigurationException(e); - } catch (final KeyStoreException e) { - LOGGER.error("Failed to initialize the TrustManagerFactory", e); - throw new TrustStoreConfigurationException(e); + factory.init(config.getKeyStore(), password); + } finally { + config.clearSecrets(); } + return factory.getKeyManagers(); } - private KeyManagerFactory loadKeyManagerFactory() throws KeyStoreConfigurationException { - if (keyStoreConfig == null) { - throw new KeyStoreConfigurationException(new Exception("The keyStoreConfiguration is null")); - } - - try { - return keyStoreConfig.initKeyManagerFactory(); - } catch (final NoSuchAlgorithmException e) { - LOGGER.error("The specified algorithm is not available from the specified provider", e); - throw new KeyStoreConfigurationException(e); - } catch (final KeyStoreException e) { - LOGGER.error("Failed to initialize the TrustManagerFactory", e); - throw new KeyStoreConfigurationException(e); - } catch (final UnrecoverableKeyException e) { - LOGGER.error("The key cannot be recovered (e.g. the given password is wrong)", e); - throw new KeyStoreConfigurationException(e); + private static TrustManager[] loadTrustManagers(@Nullable final TrustStoreConfiguration config) throws Exception { + if (config == null) { + return new TrustManager[0]; } + final TrustManagerFactory factory = TrustManagerFactory.getInstance(config.getTrustManagerFactoryAlgorithm()); + factory.init(config.getKeyStore()); + return factory.getTrustManagers(); } /** * Creates an SslConfiguration from a KeyStoreConfiguration and a TrustStoreConfiguration. * * @param protocol The protocol, see SSLContext Algorithms - * @param keyStoreConfig The KeyStoreConfiguration. + * @param keyStoreConfig The KeyStoreConfiguration. * @param trustStoreConfig The TrustStoreConfiguration. * @return a new SslConfiguration */ + @NullUnmarked @PluginFactory public static SslConfiguration createSSLConfiguration( // @formatter:off @@ -229,7 +155,7 @@ public static SslConfiguration createSSLConfiguration( @PluginElement final KeyStoreConfiguration keyStoreConfig, @PluginElement final TrustStoreConfiguration trustStoreConfig) { // @formatter:on - return new SslConfiguration(protocol, keyStoreConfig, trustStoreConfig, false); + return new SslConfiguration(protocol, false, keyStoreConfig, trustStoreConfig); } /** @@ -242,6 +168,7 @@ public static SslConfiguration createSSLConfiguration( * @return a new SslConfiguration * @since 2.12 */ + @NullUnmarked public static SslConfiguration createSSLConfiguration( // @formatter:off @PluginAttribute final String protocol, @@ -249,18 +176,12 @@ public static SslConfiguration createSSLConfiguration( @PluginElement final TrustStoreConfiguration trustStoreConfig, @PluginAttribute final boolean verifyHostName) { // @formatter:on - return new SslConfiguration(protocol, keyStoreConfig, trustStoreConfig, verifyHostName); + return new SslConfiguration(protocol, verifyHostName, keyStoreConfig, trustStoreConfig); } @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((keyStoreConfig == null) ? 0 : keyStoreConfig.hashCode()); - result = prime * result + ((protocol == null) ? 0 : protocol.hashCode()); - result = prime * result + ((sslContext == null) ? 0 : sslContext.hashCode()); - result = prime * result + ((trustStoreConfig == null) ? 0 : trustStoreConfig.hashCode()); - return result; + return Objects.hash(keyStoreConfig, protocol, sslContext, trustStoreConfig); } @Override @@ -275,37 +196,29 @@ public boolean equals(final Object obj) { return false; } final SslConfiguration other = (SslConfiguration) obj; - if (keyStoreConfig == null) { - if (other.keyStoreConfig != null) { - return false; - } - } else if (!keyStoreConfig.equals(other.keyStoreConfig)) { + if (!Objects.equals(protocol, other.protocol)) { return false; } - if (protocol == null) { - if (other.protocol != null) { - return false; - } - } else if (!protocol.equals(other.protocol)) { + if (!Objects.equals(verifyHostName, other.verifyHostName)) { return false; } - if (sslContext == null) { - if (other.sslContext != null) { - return false; - } - } else if (!sslContext.equals(other.sslContext)) { + if (!Objects.equals(keyStoreConfig, other.keyStoreConfig)) { return false; } - if (trustStoreConfig == null) { - if (other.trustStoreConfig != null) { - return false; - } - } else if (!trustStoreConfig.equals(other.trustStoreConfig)) { + if (!Objects.equals(trustStoreConfig, other.trustStoreConfig)) { return false; } return true; } + public String getProtocol() { + return protocol; + } + + public boolean isVerifyHostName() { + return verifyHostName; + } + public KeyStoreConfiguration getKeyStoreConfig() { return keyStoreConfig; } @@ -317,12 +230,4 @@ public TrustStoreConfiguration getTrustStoreConfig() { public SSLContext getSslContext() { return sslContext; } - - public String getProtocol() { - return protocol; - } - - public boolean isVerifyHostName() { - return verifyHostName; - } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactory.java index cd0c2803fa8..29994560403 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactory.java @@ -17,6 +17,7 @@ package org.apache.logging.log4j.core.net.ssl; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.CoreProperties.KeyStoreProperties; import org.apache.logging.log4j.core.impl.CoreProperties.TransportSecurityProperties; import org.apache.logging.log4j.kit.env.PropertyEnvironment; import org.apache.logging.log4j.status.StatusLogger; @@ -38,24 +39,20 @@ static SslConfiguration getSslConfiguration(final TransportSecurityProperties co KeyStoreConfiguration keyStoreConfiguration = null; TrustStoreConfiguration trustStoreConfiguration = null; if (config != null) { - if (config.trustStore().location() != null) { + final KeyStoreProperties trustStoreProperties = config.trustStore(); + if (trustStoreProperties.location() != null || trustStoreProperties.type() != null) { try { - trustStoreConfiguration = TrustStoreConfiguration.createKeyStoreConfiguration(config.trustStore()); - } catch (final Exception ex) { - LOGGER.warn( - "Unable to create trust store configuration due to: {} {}", - ex.getClass().getName(), - ex.getMessage()); + trustStoreConfiguration = TrustStoreConfiguration.createKeyStoreConfiguration(trustStoreProperties); + } catch (final Exception error) { + LOGGER.error("Failed to create the trust store configuration", error); } } - if (config.keyStore().location() != null) { + final KeyStoreProperties keyStoreProperties = config.keyStore(); + if (keyStoreProperties.location() != null || keyStoreProperties.type() != null) { try { - keyStoreConfiguration = KeyStoreConfiguration.createKeyStoreConfiguration(config.keyStore()); - } catch (final Exception ex) { - LOGGER.warn( - "Unable to create key store configuration due to: {} {}", - ex.getClass().getName(), - ex.getMessage()); + keyStoreConfiguration = KeyStoreConfiguration.createKeyStoreConfiguration(keyStoreProperties); + } catch (final Exception error) { + LOGGER.error("Failed to create the key store configuration", error); } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java index a32ccd545b5..28bc1a7d898 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java @@ -39,7 +39,7 @@ public StoreConfiguration(final String location, final PasswordProvider password */ public void clearSecrets() { this.location = null; - this.passwordProvider = null; + this.passwordProvider = new MemoryPasswordProvider(new char[0]); } public String getLocation() { @@ -58,13 +58,6 @@ public void setPassword(final char[] password) { this.passwordProvider = new MemoryPasswordProvider(password); } - /** - * @throws StoreConfigurationException May be thrown by subclasses - */ - protected T load() throws StoreConfigurationException { - return null; - } - @Override public int hashCode() { final int prime = 31; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java index 3e33ce80d1e..ccda1aead0c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java @@ -17,9 +17,8 @@ package org.apache.logging.log4j.core.net.ssl; import java.nio.file.Path; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.Objects; import javax.net.ssl.TrustManagerFactory; import org.apache.logging.log4j.core.impl.CoreProperties; import org.apache.logging.log4j.plugins.Configurable; @@ -121,21 +120,6 @@ public static TrustStoreConfiguration createKeyStoreConfiguration( } } - public TrustManagerFactory initTrustManagerFactory() throws NoSuchAlgorithmException, KeyStoreException { - final TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(this.trustManagerFactoryAlgorithm); - tmFactory.init(this.getKeyStore()); - return tmFactory; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = - prime * result + ((trustManagerFactoryAlgorithm == null) ? 0 : trustManagerFactoryAlgorithm.hashCode()); - return result; - } - @Override public boolean equals(final Object obj) { if (this == obj) { @@ -148,11 +132,7 @@ public boolean equals(final Object obj) { return false; } final TrustStoreConfiguration other = (TrustStoreConfiguration) obj; - if (trustManagerFactoryAlgorithm == null) { - if (other.trustManagerFactoryAlgorithm != null) { - return false; - } - } else if (!trustManagerFactoryAlgorithm.equals(other.trustManagerFactoryAlgorithm)) { + if (!Objects.equals(trustManagerFactoryAlgorithm, other.trustManagerFactoryAlgorithm)) { return false; } return true;