File log4j-CVE-2025-68161.patch of Package log4j

diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java
index 9f0f5f5e91..a5deb220cc 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java
@@ -22,8 +22,17 @@ import java.io.Serializable;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
+import java.security.KeyStoreException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Enumeration;
 import java.util.List;
-
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.net.ssl.SNIHostName;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 
@@ -31,36 +40,57 @@ import org.apache.logging.log4j.core.Layout;
 import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
 import org.apache.logging.log4j.util.Strings;
 
-/**
- *
- */
 public class SslSocketManager extends TcpSocketManager {
+
     public static final int DEFAULT_PORT = 6514;
+
     private static final SslSocketManagerFactory FACTORY = new SslSocketManagerFactory();
+
     private final SslConfiguration sslConfig;
 
     /**
     *
     *
-    * @param name          The unique name of this connection.
-    * @param os            The OutputStream.
-    * @param sock          The Socket.
-    * @param inetAddress          The Internet address of the host.
-    * @param host          The name of the host.
-    * @param port          The port number on the host.
-    * @param connectTimeoutMillis the connect timeout in milliseconds.
+     * @param name          the unique name of this connection
+     * @param os            the OutputStream
+     * @param sock          the Socket
+     * @param inetAddress          the Internet address of the host
+     * @param host          the name of the host
+     * @param port          the port number on the host
+     * @param connectTimeoutMillis the connect timeout in milliseconds
     * @param reconnectionDelayMillis         Reconnection interval.
-    * @param immediateFail
-    * @param layout        The Layout.
+     * @param immediateFail True if the write should fail if no socket is immediately available.
+     * @param layout        the Layout
     * @param bufferSize The buffer size.
     * @deprecated Use {@link #SslSocketManager(String, OutputStream, Socket, SslConfiguration, InetAddress, String, int, int, int, boolean, Layout, int, SocketOptions)}.
     */
    @Deprecated
-   public SslSocketManager(final String name, final OutputStream os, final Socket sock,
-           final SslConfiguration sslConfig, final InetAddress inetAddress, final String host, final int port,
-           final int connectTimeoutMillis, final int reconnectionDelayMillis, final boolean immediateFail,
-           final Layout<? extends Serializable> layout, final int bufferSize) {
-       super(name, os, sock, inetAddress, host, port, connectTimeoutMillis, reconnectionDelayMillis, immediateFail, layout, bufferSize, null);
+    public SslSocketManager(
+            final String name,
+            final OutputStream os,
+            final Socket sock,
+            final SslConfiguration sslConfig,
+            final InetAddress inetAddress,
+            final String host,
+            final int port,
+            final int connectTimeoutMillis,
+            final int reconnectionDelayMillis,
+            final boolean immediateFail,
+            final Layout<? extends Serializable> layout,
+            final int bufferSize) {
+        super(
+                name,
+                os,
+                sock,
+                inetAddress,
+                host,
+                port,
+                connectTimeoutMillis,
+                reconnectionDelayMillis,
+                immediateFail,
+                layout,
+                bufferSize,
+                null);
        this.sslConfig = sslConfig;
    }
 
@@ -75,25 +105,61 @@ public class SslSocketManager extends TcpSocketManager {
    * @param port          The port number on the host.
    * @param connectTimeoutMillis the connect timeout in milliseconds.
    * @param reconnectionDelayMillis         Reconnection interval.
-   * @param immediateFail
+     * @param immediateFail True if the write should fail if no socket is immediately available.
    * @param layout        The Layout.
    * @param bufferSize The buffer size.
    */
-  public SslSocketManager(final String name, final OutputStream os, final Socket sock,
-          final SslConfiguration sslConfig, final InetAddress inetAddress, final String host, final int port,
-          final int connectTimeoutMillis, final int reconnectionDelayMillis, final boolean immediateFail,
-          final Layout<? extends Serializable> layout, final int bufferSize, final SocketOptions socketOptions) {
-      super(name, os, sock, inetAddress, host, port, connectTimeoutMillis, reconnectionDelayMillis, immediateFail, layout, bufferSize, socketOptions);
+    public SslSocketManager(
+            final String name,
+            final OutputStream os,
+            final Socket sock,
+            final SslConfiguration sslConfig,
+            final InetAddress inetAddress,
+            final String host,
+            final int port,
+            final int connectTimeoutMillis,
+            final int reconnectionDelayMillis,
+            final boolean immediateFail,
+            final Layout<? extends Serializable> layout,
+            final int bufferSize,
+            final SocketOptions socketOptions) {
+        super(
+                name,
+                os,
+                sock,
+                inetAddress,
+                host,
+                port,
+                connectTimeoutMillis,
+                reconnectionDelayMillis,
+                immediateFail,
+                layout,
+                bufferSize,
+                socketOptions);
       this.sslConfig = sslConfig;
   }
 
     private static class SslFactoryData extends FactoryData {
         protected SslConfiguration sslConfiguration;
 
-        public SslFactoryData(final SslConfiguration sslConfiguration, final String host, final int port,
-                final int connectTimeoutMillis, final int reconnectDelayMillis, final boolean immediateFail,
-                final Layout<? extends Serializable> layout, final int bufferSize, final SocketOptions socketOptions) {
-            super(host, port, connectTimeoutMillis, reconnectDelayMillis, immediateFail, layout, bufferSize,
+        public SslFactoryData(
+                final SslConfiguration sslConfiguration,
+                final String host,
+                final int port,
+                final int connectTimeoutMillis,
+                final int reconnectDelayMillis,
+                final boolean immediateFail,
+                final Layout<? extends Serializable> layout,
+                final int bufferSize,
+                final SocketOptions socketOptions) {
+            super(
+                    host,
+                    port,
+                    connectTimeoutMillis,
+                    reconnectDelayMillis,
+                    immediateFail,
+                    layout,
+                    bufferSize,
                     socketOptions);
             this.sslConfiguration = sslConfiguration;
         }
@@ -111,15 +177,39 @@ public class SslSocketManager extends TcpSocketManager {
      * @deprecated Use {@link SslSocketManager#getSocketManager(SslConfiguration, String, int, int, int, boolean, Layout, int, SocketOptions)}.
      */
     @Deprecated
-    public static SslSocketManager getSocketManager(final SslConfiguration sslConfig, final String host, final int port,
-            final int connectTimeoutMillis, final int reconnectDelayMillis, final boolean immediateFail,
-            final Layout<? extends Serializable> layout, final int bufferSize) {
-        return getSocketManager(sslConfig, host, port, connectTimeoutMillis, reconnectDelayMillis, immediateFail, layout, bufferSize, null);
+    public static SslSocketManager getSocketManager(
+            final SslConfiguration sslConfig,
+            final String host,
+            final int port,
+            final int connectTimeoutMillis,
+            final int reconnectDelayMillis,
+            final boolean immediateFail,
+            final Layout<? extends Serializable> layout,
+            final int bufferSize) {
+        return getSocketManager(
+                sslConfig,
+                host,
+                port,
+                connectTimeoutMillis,
+                reconnectDelayMillis,
+                immediateFail,
+                layout,
+                bufferSize,
+                null);
     }
 
-    public static SslSocketManager getSocketManager(final SslConfiguration sslConfig, final String host, int port,
-            final int connectTimeoutMillis, int reconnectDelayMillis, final boolean immediateFail,
-            final Layout<? extends Serializable> layout, final int bufferSize, final SocketOptions socketOptions) {
+    public static SslSocketManager getSocketManager(
+            final SslConfiguration sslConfig,
+            final String host,
+            int port,
+            final int connectTimeoutMillis,
+            int reconnectDelayMillis,
+            final boolean immediateFail,
+            final Layout<? extends Serializable> layout,
+            final int bufferSize,
+            final SocketOptions socketOptions) {
+
+        // Check arguments
         if (Strings.isEmpty(host)) {
             throw new IllegalArgumentException("A host name is required");
         }
@@ -129,71 +219,215 @@ public class SslSocketManager extends TcpSocketManager {
         if (reconnectDelayMillis == 0) {
             reconnectDelayMillis = DEFAULT_RECONNECTION_DELAY_MILLIS;
         }
-        final String name = "TLS:" + host + ':' + port;
-        return (SslSocketManager) getManager(name, new SslFactoryData(sslConfig, host, port, connectTimeoutMillis,
-                reconnectDelayMillis, immediateFail, layout, bufferSize, socketOptions), FACTORY);
+
+        // Create an ID associated with the SSL configuration. This is necessary to make sure a new `name` is generated
+        // (and consequently a new connection pool is created) upon reconfiguration with a different configuration;
+        // e.g., change in the SSL certificate content, even though the certificate file locations are still the same.
+        // See #2767 and LOG4J2-2988 for details.
+        final String sslConfigId = createSslConfigurationId(sslConfig);
+        final String name = String.format("%s:%s:%d:%s", sslConfig.getProtocol(), host, port, sslConfigId);
+
+        return (SslSocketManager) getManager(
+                name,
+                new SslFactoryData(
+                        sslConfig,
+                        host,
+                        port,
+                        connectTimeoutMillis,
+                        reconnectDelayMillis,
+                        immediateFail,
+                        layout,
+                        bufferSize,
+                        socketOptions),
+                FACTORY);
+    }
+
+    /**
+     * Creates a unique identifier using the certificate issuers and serial numbers of the given SSL configuration.
+     *
+     * @param sslConfig an SSL configuration
+     * @return a unique identifier extracted from the given SSL configuration
+     */
+    private static String createSslConfigurationId(final SslConfiguration sslConfig) {
+        return String.valueOf(Stream.of(sslConfig.getKeyStoreConfig(), sslConfig.getTrustStoreConfig())
+                .filter(Objects::nonNull)
+                .flatMap(keyStoreConfig -> {
+                    final Enumeration<String> 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);
-        final Socket newSocket = socketFactory.createSocket();
-        newSocket.connect(socketAddress, getConnectTimeoutMillis());
-        return newSocket;
+        return createSocket(getHost(), socketAddress, getConnectTimeoutMillis(), sslConfig, getSocketOptions());
     }
 
     private static SSLSocketFactory createSslSocketFactory(final SslConfiguration sslConf) {
-        SSLSocketFactory socketFactory;
-
         if (sslConf != null) {
-            socketFactory = sslConf.getSslSocketFactory();
-        } else {
-            socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
+            final SSLContext sslContext = sslConf.getSslContext();
+            if (sslContext != null) {
+                return sslContext.getSocketFactory();
         }
-
-        return socketFactory;
     }
-
+        return (SSLSocketFactory) SSLSocketFactory.getDefault();
+    }
 
     private static class SslSocketManagerFactory extends TcpSocketManagerFactory<SslSocketManager, SslFactoryData> {
 
         @Override
-        SslSocketManager createManager(final String name, final OutputStream os, final Socket socket, final InetAddress inetAddress,
+        SslSocketManager createManager(
+                final String name,
+                final OutputStream os,
+                final Socket socket,
+                final InetAddress inetAddress,
                 final SslFactoryData data) {
-            return new SslSocketManager(name, os, socket, data.sslConfiguration, inetAddress, data.host, data.port,
-                    data.connectTimeoutMillis, data.reconnectDelayMillis, data.immediateFail, data.layout, data.bufferSize,
+            return new SslSocketManager(
+                    name,
+                    os,
+                    socket,
+                    data.sslConfiguration,
+                    inetAddress,
+                    data.host,
+                    data.port,
+                    data.connectTimeoutMillis,
+                    data.reconnectDelayMillis,
+                    data.immediateFail,
+                    data.layout,
+                    data.bufferSize,
                     data.socketOptions);
         }
 
         @Override
         Socket createSocket(final SslFactoryData data) throws IOException {
-            List<InetSocketAddress> socketAddresses = RESOLVER.resolveHost(data.host, data.port);
+            final List<InetSocketAddress> socketAddresses = RESOLVER.resolveHost(data.host, data.port);
             IOException ioe = null;
             for (InetSocketAddress socketAddress : socketAddresses) {
                 try {
-                    return SslSocketManager.createSocket(socketAddress, data.connectTimeoutMillis,
-                            data.sslConfiguration, data.socketOptions);
+                    return SslSocketManager.createSocket(
+                            data.host,
+                            socketAddress,
+                            data.connectTimeoutMillis,
+                            data.sslConfiguration,
+                            data.socketOptions);
                 } catch (IOException ex) {
-                    ioe = ex;
+                    final String message = String.format(
+                            "failed create a socket to `%s:%s` that is resolved to address `%s`",
+                            data.host, data.port, socketAddress);
+                    final IOException newEx = new IOException(message, ex);
+                    if (ioe == null) {
+                        ioe = newEx;
+                    } else {
+                        ioe.addSuppressed(newEx);
+                    }
                 }
             }
             throw new IOException(errorMessage(data, socketAddresses) , ioe);
         }
     }
 
-    static Socket createSocket(final InetSocketAddress socketAddress, final int connectTimeoutMillis,
-            final SslConfiguration sslConfiguration, final SocketOptions socketOptions) throws IOException {
+    private static Socket createSocket(
+            final String hostName,
+            final InetSocketAddress socketAddress,
+            final int connectTimeoutMillis,
+            final SslConfiguration sslConfiguration,
+            final SocketOptions socketOptions)
+            throws IOException {
+
+        // Create the `SSLSocket`
         final SSLSocketFactory socketFactory = createSslSocketFactory(sslConfiguration);
         final SSLSocket socket = (SSLSocket) socketFactory.createSocket();
+
+        // Apply socket options before `connect()`
         if (socketOptions != null) {
-            // Not sure which options must be applied before or after the connect() call.
             socketOptions.apply(socket);
         }
+
+        // Connect the socket
         socket.connect(socketAddress, connectTimeoutMillis);
-        if (socketOptions != null) {
-            // Not sure which options must be applied before or after the connect() call.
-            socketOptions.apply(socket);
+
+        // Verify the host name
+        if (sslConfiguration.isVerifyHostName()) {
+            // Allowed endpoint identification algorithms: HTTPS and LDAPS.
+            // https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#endpoint-identification-algorithms
+            final SSLParameters sslParameters = socket.getSSLParameters();
+            sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
+            final SNIHostName serverName = createSniHostName(hostName);
+            if (serverName != null) {
+                sslParameters.setServerNames(Collections.singletonList(serverName));
+            }
+            socket.setSSLParameters(sslParameters);
         }
+
+        // Force the handshake right after `connect()` instead of waiting for read/write to trigger it indirectly at a
+        // later stage
+        socket.startHandshake();
+
         return socket;
     }
+
+    /**
+     * {@return an {@link SNIHostName} instance if the provided host name is not an IP literal (RFC 6066), and constitutes a valid host name (RFC 1035); null otherwise}
+     *
+     * @param hostName a host name
+     *
+     * @see <a href="https://www.rfc-editor.org/rfc/rfc6066.html#section-3">Literal IPv4 and IPv6 addresses are not permitted in "HostName" (RFC 6066)</a>
+     * @see <a href="https://www.rfc-editor.org/rfc/rfc1035.html">Domain Names - Implementation and Specification (RFC 1035)</a>
+     */
+    static SNIHostName createSniHostName(String hostName) {
+        // The actual check should be
+        //
+        //     !isIPv4(h) && !isIPv6(h) && isValidHostName(h)
+        //
+        // Though we translate this into
+        //
+        //     !h.matches("\d+[.]\d+[.]\d+[.]\d+") && new SNIServerName(h)
+        //
+        // This simplification is possible because
+        //
+        // - The `\d+[.]\d+[.]\d+[.]\d+` is sufficient to eliminate IPv4 addresses.
+        //   Any sequence of four numeric labels (e.g., `1234.2345.3456.4567`) is not a valid host name.
+        //   Hence, false positives are not a problem, they would be eliminated by `isValidHostName()` anyway.
+        //
+        // - `SNIServerName::new` throws an exception on invalid host names.
+        //   This check is performed using `IDN.toASCII(hostName, IDN.USE_STD3_ASCII_RULES)`.
+        //   IPv6 literals don't qualify as a valid host name by `IDN::toASCII`.
+        //   This assumption on `IDN` is unlikely to change in the foreseeable future.
+        if (!hostName.matches("\\d+[.]\\d+[.]\\d+[.]\\d+")) {
+            try {
+                return new SNIHostName(hostName);
+            } catch (IllegalArgumentException ignored) {
+                // Do nothing
+            }
+        }
+        return null;
+    }
 }
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 fa23b89759..8a967d6294 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,12 +16,8 @@
  */
 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;
@@ -42,20 +38,30 @@ import org.apache.logging.log4j.status.StatusLogger;
  */
 @Plugin(name = "Ssl", category = Core.CATEGORY_NAME, printObject = true)
 public class SslConfiguration {
+
     private static final StatusLogger LOGGER = StatusLogger.getLogger();
-    private final KeyStoreConfiguration keyStoreConfig;
-    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, boolean verifyHostName) {
+    private final KeyStoreConfiguration keyStoreConfig;
+
+    private final TrustStoreConfiguration trustStoreConfig;
+
+    private final transient SSLContext sslContext;
+
+    private SslConfiguration(
+            final String protocol,
+            final boolean verifyHostName,
+            final KeyStoreConfiguration keyStoreConfig,
+            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);
     }
 
     /**
@@ -70,160 +76,91 @@ public class SslConfiguration {
         }
     }
 
+    /**
+     * Gets the SSL socket factory of the configured SSL context.
+     *
+     * @return the SSL socket factory of the configured SSL context
+     * @deprecated Use {@link SSLContext#getSocketFactory()} on {@link #getSslContext()}
+     */
+    @Deprecated
     public SSLSocketFactory getSslSocketFactory() {
-        return sslContext.getSocketFactory();
+        return sslContext != null ? sslContext.getSocketFactory() : null;
     }
 
+    /**
+     * Gets the SSL server socket factory of the configured SSL context.
+     *
+     * @return the SSL server socket factory of the configured SSL context
+     * @deprecated Use {@link SSLContext#getServerSocketFactory()} on {@link #getSslContext()}
+     */
+    @Deprecated
     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;
-        }
+        return sslContext != null ? sslContext.getServerSocketFactory() : 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);
+        } 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 {
-        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);
+    private static SSLContext createSslContext(
+            final String protocol,
+            final KeyStoreConfiguration keyStoreConfig,
+            final TrustStoreConfiguration trustStoreConfig) {
+        try {
+            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(final KeyStoreConfiguration config) throws Exception {
+        if (config == null) {
+            return null;
         }
-
+        final KeyManagerFactory factory = KeyManagerFactory.getInstance(config.getKeyManagerFactoryAlgorithm());
+        final char[] password = config.getPasswordAsCharArray();
         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();
         }
-    }
-
-    private KeyManagerFactory loadKeyManagerFactory() throws KeyStoreConfigurationException {
-        if (keyStoreConfig == null) {
-            throw new KeyStoreConfigurationException(new Exception("The keyStoreConfiguration is null"));
+        return factory.getKeyManagers();
         }
 
-        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(final TrustStoreConfiguration config) throws Exception {
+        if (config == null) {
+            return null;
         }
+        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 http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
+     * @param protocol         The protocol, see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">SSLContext Algorithms</a>
      * @param keyStoreConfig The KeyStoreConfiguration.
      * @param trustStoreConfig The TrustStoreConfiguration.
      * @return a new SslConfiguration
@@ -235,13 +172,13 @@ public class SslConfiguration {
             @PluginElement("KeyStore") final KeyStoreConfiguration keyStoreConfig,
             @PluginElement("TrustStore") final TrustStoreConfiguration trustStoreConfig) {
             // @formatter:on
-        return new SslConfiguration(protocol, keyStoreConfig, trustStoreConfig, false);
+        return new SslConfiguration(protocol, false, keyStoreConfig, trustStoreConfig);
     }
 
     /**
      * Creates an SslConfiguration from a KeyStoreConfiguration and a TrustStoreConfiguration.
      *
-     * @param protocol The protocol, see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
+     * @param protocol The protocol, see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">SSLContext Algorithms</a>
      * @param keyStoreConfig The KeyStoreConfiguration.
      * @param trustStoreConfig The TrustStoreConfiguration.
      * @param verifyHostName whether or not to perform host name verification
@@ -255,7 +192,7 @@ public class SslConfiguration {
         @PluginElement("TrustStore") final TrustStoreConfiguration trustStoreConfig,
         @PluginAttribute("verifyHostName") final boolean verifyHostName) {
         // @formatter:on
-        return new SslConfiguration(protocol, keyStoreConfig, trustStoreConfig, verifyHostName);
+        return new SslConfiguration(protocol, verifyHostName, keyStoreConfig, trustStoreConfig);
     }
 
     @Override
@@ -275,13 +212,13 @@ public class SslConfiguration {
             return false;
         }
         final SslConfiguration other = (SslConfiguration) obj;
-        if (!Objects.equals(keyStoreConfig, other.keyStoreConfig)) {
+        if (!Objects.equals(protocol, other.protocol)) {
             return false;
         }
-        if (!Objects.equals(protocol, other.protocol)) {
+        if (!Objects.equals(verifyHostName, other.verifyHostName)) {
             return false;
         }
-        if (!Objects.equals(sslContext, other.sslContext)) {
+        if (!Objects.equals(keyStoreConfig, other.keyStoreConfig)) {
             return false;
         }
         if (!Objects.equals(trustStoreConfig, other.trustStoreConfig)) {
@@ -290,6 +227,14 @@ public class SslConfiguration {
         return true;
     }
 
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public boolean isVerifyHostName() {
+        return verifyHostName;
+    }
+
     public KeyStoreConfiguration getKeyStoreConfig() {
         return keyStoreConfig;
     }
@@ -301,12 +246,4 @@ public class SslConfiguration {
     public SSLContext getSslContext() {
         return sslContext;
     }
-
-    public String getProtocol() {
-        return protocol;
-    }
-
-    public boolean isVerifyHostName() {
-        return verifyHostName;
-    }
 }
openSUSE Build Service is sponsored by