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;