diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/ClientCnxnSocketNetty.java b/zookeeper-server/src/main/java/org/apache/zookeeper/ClientCnxnSocketNetty.java index 82364dcb55b..cc8f5a90836 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/ClientCnxnSocketNetty.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/ClientCnxnSocketNetty.java @@ -443,7 +443,8 @@ protected void initChannel(SocketChannel ch) throws Exception { // The synchronized is to prevent the race on shared variable "sslContext". // Basically we only need to create it once. private synchronized void initSSL(ChannelPipeline pipeline) - throws X509Exception.KeyManagerException, X509Exception.TrustManagerException, SSLException { + throws X509Exception.SSLContextException, X509Exception.KeyManagerException, + X509Exception.TrustManagerException, SSLException { if (sslContext == null) { try (ClientX509Util x509Util = new ClientX509Util()) { sslContext = x509Util.createNettySslContextForClient(clientConfig); diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java index 1e50b84257c..82b1bb3fd1e 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java @@ -19,6 +19,8 @@ package org.apache.zookeeper.common; import io.netty.handler.ssl.DelegatingSslContext; +import io.netty.handler.ssl.IdentityCipherSuiteFilter; +import io.netty.handler.ssl.JdkSslContext; import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; @@ -26,6 +28,7 @@ import java.security.Security; import java.util.Arrays; import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; @@ -62,7 +65,13 @@ public String getSslProviderProperty() { } public SslContext createNettySslContextForClient(ZKConfig config) - throws X509Exception.KeyManagerException, X509Exception.TrustManagerException, SSLException { + throws X509Exception.SSLContextException, X509Exception.KeyManagerException, + X509Exception.TrustManagerException, SSLException { + SSLContext suppliedSSLContext = loadSuppliedSSLContext(config); + if (suppliedSSLContext != null) { + return createNettyJdkSslContext(config, suppliedSSLContext, true); + } + SslContextBuilder sslContextBuilder = SslContextBuilder.forClient(); KeyManager km = buildKeyManager(config); @@ -97,6 +106,11 @@ public SslContext createNettySslContextForClient(ZKConfig config) public SslContext createNettySslContextForServer(ZKConfig config) throws X509Exception.SSLContextException, X509Exception.KeyManagerException, X509Exception.TrustManagerException, SSLException { + SSLContext suppliedSSLContext = loadSuppliedSSLContext(config); + if (suppliedSSLContext != null) { + return createNettyJdkSslContext(config, suppliedSSLContext, false); + } + KeyManager km = buildKeyManager(config); if (km == null) { throw new X509Exception.SSLContextException( @@ -133,6 +147,54 @@ public SslContext createNettySslContextForServer(ZKConfig config, KeyManager key } } + /** + * Wraps a user supplied {@link SSLContext} in a Netty {@link SslContext}, applying the configured + * protocols, cipher suites, client auth mode and hostname verification on top of it. + * + *
A supplied SSLContext carries its own key and trust managers, so it can only be used with the + * JDK SSL provider: the OpenSSL providers build their own native context and cannot delegate to it. + * + *
Unlike the file based path, hostname verification is applied whenever it is enabled. The file
+ * based path relies on {@link ZKTrustManager} to verify hostnames and only falls back to endpoint
+ * identification when no trust manager is available, which is never the case for a supplied context.
+ *
+ * @param config the configuration to read the SSL options from.
+ * @param sslContext the user supplied SSLContext.
+ * @param isClient {@code true} to create a client side context, {@code false} for server side.
+ * @return the Netty SslContext.
+ * @throws X509Exception.SSLContextException if a non JDK SSL provider is configured.
+ */
+ private SslContext createNettyJdkSslContext(ZKConfig config, SSLContext sslContext, boolean isClient)
+ throws X509Exception.SSLContextException {
+ SslProvider sslProvider = getSslProvider(config);
+ if (sslProvider != SslProvider.JDK) {
+ throw new X509Exception.SSLContextException("An SSLContext supplied through "
+ + getSslContextSupplierClassProperty()
+ + " can only be used with the JDK SSL provider, but "
+ + getSslProviderProperty()
+ + " is set to "
+ + sslProvider);
+ }
+
+ SslContext nettySslContext = new JdkSslContext(
+ sslContext,
+ isClient,
+ getCipherSuites(config),
+ IdentityCipherSuiteFilter.INSTANCE,
+ null,
+ isClient ? X509Util.ClientAuth.NONE.toNettyClientAuth() : getClientAuth(config).toNettyClientAuth(),
+ getEnabledProtocols(config),
+ false);
+
+ boolean hostnameVerificationEnabled = isClient
+ ? isServerHostnameVerificationEnabled(config)
+ : isClientHostnameVerificationEnabled(config);
+ if (hostnameVerificationEnabled) {
+ return addHostnameVerification(nettySslContext, isClient ? "Server" : "Client");
+ }
+ return nettySslContext;
+ }
+
private SslContextBuilder handleTcnativeOcspStapling(SslContextBuilder builder, ZKConfig config) {
SslProvider sslProvider = getSslProvider(config);
boolean tcnative = sslProvider == SslProvider.OPENSSL || sslProvider == SslProvider.OPENSSL_REFCNT;
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
index 460a6f3ac55..2a91e87d217 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
@@ -360,30 +360,47 @@ public int getSslHandshakeTimeoutMillis() {
}
}
- @SuppressWarnings("unchecked")
public SSLContextAndOptions createSSLContextAndOptions(ZKConfig config) throws SSLContextException {
+ final SSLContext suppliedSSLContext = loadSuppliedSSLContext(config);
+ if (suppliedSSLContext != null) {
+ return new SSLContextAndOptions(this, config, suppliedSSLContext);
+ }
+ return createSSLContextAndOptionsFromConfig(config);
+ }
+
+ /**
+ * Loads an {@link SSLContext} from the {@link Supplier} implementation named by the
+ * {@link #getSslContextSupplierClassProperty()} property. This allows a user to take full control over
+ * the construction of the SSLContext, for example to use a hardware key store or an SSLContext obtained
+ * from a container, rather than having ZooKeeper load key material from files.
+ *
+ * @param config the configuration to read the supplier class name from.
+ * @return the supplied SSLContext, or {@code null} if the property is not set.
+ * @throws SSLContextException if the supplier class cannot be loaded, instantiated or invoked.
+ */
+ @SuppressWarnings("unchecked")
+ protected SSLContext loadSuppliedSSLContext(ZKConfig config) throws SSLContextException {
final String supplierContextClassName = config.getProperty(sslContextSupplierClassProperty);
- if (supplierContextClassName != null) {
- LOG.debug("Loading SSLContext supplier from property '{}'", sslContextSupplierClassProperty);
+ if (supplierContextClassName == null) {
+ return null;
+ }
+ LOG.debug("Loading SSLContext supplier from property '{}'", sslContextSupplierClassProperty);
- try {
- Class> sslContextClass = Class.forName(supplierContextClassName);
- Supplier