forked from pool/log4j
806 lines
36 KiB
Diff
806 lines
36 KiB
Diff
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;
|
|
- }
|
|
}
|