zookeeper-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From an...@apache.org
Subject [3/3] zookeeper git commit: ZOOKEEPER-236: SSL Support for Atomic Broadcast protocol
Date Fri, 05 Oct 2018 10:44:11 GMT
ZOOKEEPER-236: SSL Support for Atomic Broadcast protocol

This is a work in progress, I wanted to get some feedback from the community while I worked on this. Please do not merge yet. Tests, documentation, and some cleanup still coming.

This is a first pass at ssl support for the zookeeper quorum. It supports encrypting both leader election and normal operation.

Rolling upgrades are supported via port unification (`portUnification=true`). This should only be used while performing a rolling upgrade.

Some open questions:
- Anyone have any ideas for better names for the configuration options (`sslQuorum` and `portUnification` currently).
- I am using the same configuration that points to the truststore/keystore used for server <-> client ssl. Do they need to be separate?
- Is port unification the correct approach for rolling upgrades? Is the impact from the use of `BufferedSocket`s during the upgrade acceptable? See: http://stackoverflow.com/questions/25637039/detecting-ssl-connection-and-converting-socket-to-sslsocket http://stackoverflow.com/questions/6559859/is-it-possible-to-change-plain-socket-to-sslsocket
- server <-> client ssl is implemented with netty. I did not feel that rewriting our server <-> server logic with netty was necessary given how easy ssl was to implement with standard java `SSLSocket`s. Any arguments to the contrary?

Thanks,
Abe

Author: Andor Molnar <andor@cloudera.com>
Author: Andor Molnar <andor@apache.org>

Reviewers: hanm@apache.org, andor@apache.org

Closes #184 from afine/ZOOKEEPER-236 and squashes the following commits:

fdcc9151 [Andor Molnar] ZOOKEEPER-236. Replaced DefaultHostnameVerifier with custom impl
c014a54c [Andor Molnar] ZOOKEEPER-236. Temporary disabled portUnification support
e4144962 [Andor Molnar] ZOOKEEPER-236. Nit code review fixes
209fbca7 [Andor Molnar] ZOOKEEPER-236. Added new JMX properties to expose SSL quorum related settings
1f8aab05 [Andor Molnar] ZOOKEEPER-236. Revert portUnification/sslQuorum logic
a9fa6981 [Andor Molnar] ZOOKEEPER-236. Code review fixes:
777f31ac [Andor Molnar] ZOOKEEPER-236. Added Java8/Java9 default cipher suites
e8a17297 [Andor Molnar] ZOOKEEPER-236. Reverted to use single property for hostname verification
d64eb26f [Andor Molnar] ZOOKEEPER-236. Code review related changes: - server & client hostname verification can be set independently, - refactor defaultSSLContext to use AtomicReference, - some minor nitpicks
9ab476a7 [Andor Molnar] ZOOKEEPER-236. Trying to fix cipher suites test by changing the default protocol to TLSv1.2 and filter suitable cipher suites
ed10e88d [Andor Molnar] ZOOKEEPER-236. Added cipher suite to test to run on CentOS. Timeout in constant value. Null checks
c452d1b0 [Andor Molnar] ZOOKEEPER-236. Fixed unit test + added some extra debug logging
88b61716 [Andor Molnar] ZOOKEEPER-236: SSL Support for Atomic Broadcast protocol


Project: http://git-wip-us.apache.org/repos/asf/zookeeper/repo
Commit: http://git-wip-us.apache.org/repos/asf/zookeeper/commit/0e3b82bd
Tree: http://git-wip-us.apache.org/repos/asf/zookeeper/tree/0e3b82bd
Diff: http://git-wip-us.apache.org/repos/asf/zookeeper/diff/0e3b82bd

Branch: refs/heads/branch-3.5
Commit: 0e3b82bdf86e9214892c7e969b2e1fd744f18f03
Parents: 0383625
Author: Abraham Fine <afine@apache.org>
Authored: Fri Oct 5 12:44:02 2018 +0200
Committer: Andor Molnar <andor@apache.org>
Committed: Fri Oct 5 12:44:02 2018 +0200

----------------------------------------------------------------------
 build.xml                                       |   4 +-
 ivy.xml                                         |   3 +
 .../apache/zookeeper/ClientCnxnSocketNetty.java |   4 +-
 .../zookeeper/client/FourLetterWordMain.java    |   4 +-
 .../apache/zookeeper/common/ClientX509Util.java |  38 +
 .../apache/zookeeper/common/QuorumX509Util.java |  32 +
 .../org/apache/zookeeper/common/X509Util.java   | 325 ++++++--
 .../org/apache/zookeeper/common/ZKConfig.java   |  65 +-
 .../zookeeper/common/ZKHostnameVerifier.java    | 349 +++++++++
 .../apache/zookeeper/common/ZKTrustManager.java | 150 ++++
 .../server/NettyServerCnxnFactory.java          |  53 +-
 .../server/auth/X509AuthenticationProvider.java |  38 +-
 .../apache/zookeeper/server/quorum/Leader.java  |  28 +-
 .../apache/zookeeper/server/quorum/Learner.java |  38 +-
 .../server/quorum/PrependableSocket.java        |  49 ++
 .../zookeeper/server/quorum/QuorumBean.java     |  19 +-
 .../server/quorum/QuorumCnxManager.java         |  46 +-
 .../zookeeper/server/quorum/QuorumMXBean.java   |  10 +
 .../zookeeper/server/quorum/QuorumPeer.java     |  24 +-
 .../server/quorum/QuorumPeerConfig.java         |  21 +-
 .../zookeeper/server/quorum/QuorumPeerMain.java |   2 +
 .../server/quorum/UnifiedServerSocket.java      |  79 ++
 .../apache/zookeeper/common/X509UtilTest.java   | 264 +++++++
 .../zookeeper/common/ZKTrustManagerTest.java    | 248 ++++++
 .../zookeeper/server/quorum/LeaderBeanTest.java |   3 +-
 .../zookeeper/server/quorum/QuorumBeanTest.java |  75 ++
 .../server/quorum/QuorumPeerConfigTest.java     |   4 +-
 .../zookeeper/server/quorum/QuorumSSLTest.java  | 771 +++++++++++++++++++
 .../server/quorum/RaceConditionTest.java        |   3 +-
 .../server/quorum/UnifiedServerSocketTest.java  | 172 +++++
 .../zookeeper/server/quorum/ZabUtils.java       |   7 +-
 .../apache/zookeeper/test/ClientSSLTest.java    | 132 ++++
 .../org/apache/zookeeper/test/SSLAuthTest.java  |  40 +-
 .../test/org/apache/zookeeper/test/SSLTest.java | 130 ----
 34 files changed, 2899 insertions(+), 331 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/build.xml
----------------------------------------------------------------------
diff --git a/build.xml b/build.xml
index a52a2f7..1efa8c0 100644
--- a/build.xml
+++ b/build.xml
@@ -40,9 +40,11 @@ xmlns:cs="antlib:com.puppycrawl.tools.checkstyle.ant">
 
     <property name="junit.version" value="4.12"/>
     <property name="mockito.version" value="1.8.5"/>
-    <property name="checkstyle.version" value="6.13"/>
+    <property name="checkstyle.version" value="7.1.2"/>
     <property name="commons-collections.version" value="3.2.2"/>
 
+    <property name="bouncycastle.version" value="1.56"/>
+
     <property name="jdiff.version" value="1.0.9"/>
     <property name="xerces.version" value="1.4.4"/>
 

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/ivy.xml
----------------------------------------------------------------------
diff --git a/ivy.xml b/ivy.xml
index 3387fca..035bb4f 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -75,6 +75,9 @@
     <dependency org="commons-collections" name="commons-collections" 
                 rev="${commons-collections.version}" conf="test->default"/>
 
+    <dependency org="org.bouncycastle" name="bcprov-jdk15on" rev="${bouncycastle.version}" conf="test->default"/>
+    <dependency org="org.bouncycastle" name="bcpkix-jdk15on" rev="${bouncycastle.version}" conf="test->default"/>
+
     <dependency org="jdiff" name="jdiff" rev="${jdiff.version}"
                 conf="jdiff->default"/>
     <dependency org="xerces" name="xerces" rev="${xerces.version}"

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/ClientCnxnSocketNetty.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/ClientCnxnSocketNetty.java b/src/java/main/org/apache/zookeeper/ClientCnxnSocketNetty.java
index ec789cb..34c3db3 100755
--- a/src/java/main/org/apache/zookeeper/ClientCnxnSocketNetty.java
+++ b/src/java/main/org/apache/zookeeper/ClientCnxnSocketNetty.java
@@ -21,7 +21,7 @@ package org.apache.zookeeper;
 import org.apache.zookeeper.ClientCnxn.EndOfStreamException;
 import org.apache.zookeeper.ClientCnxn.Packet;
 import org.apache.zookeeper.client.ZKClientConfig;
-import org.apache.zookeeper.common.X509Util;
+import org.apache.zookeeper.common.ClientX509Util;
 import org.jboss.netty.bootstrap.ClientBootstrap;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.jboss.netty.buffer.ChannelBuffers;
@@ -370,7 +370,7 @@ public class ClientCnxnSocketNetty extends ClientCnxnSocket {
         // Basically we only need to create it once.
         private synchronized void initSSL(ChannelPipeline pipeline) throws SSLContextException {
             if (sslContext == null || sslEngine == null) {
-                sslContext = X509Util.createSSLContext(clientConfig);
+                sslContext = new ClientX509Util().createSSLContext(clientConfig);
                 sslEngine = sslContext.createSSLEngine(host,port);
                 sslEngine.setUseClientMode(true);
             }

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java b/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java
index b396c15..41f5e9d 100644
--- a/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java
+++ b/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java
@@ -31,9 +31,9 @@ import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 
+import org.apache.zookeeper.common.ClientX509Util;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.zookeeper.common.X509Exception.SSLContextException;
-import org.apache.zookeeper.common.X509Util;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -90,7 +90,7 @@ public class FourLetterWordMain {
             new InetSocketAddress(InetAddress.getByName(null), port);
         if (secure) {
             LOG.info("using secure socket");
-            SSLContext sslContext = X509Util.createSSLContext();
+            SSLContext sslContext = new ClientX509Util().getDefaultSSLContext();
             SSLSocketFactory socketFactory = sslContext.getSocketFactory();
             SSLSocket sslSock = (SSLSocket) socketFactory.createSocket();
             sslSock.connect(hostaddress, timeout);

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/common/ClientX509Util.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/common/ClientX509Util.java b/src/java/main/org/apache/zookeeper/common/ClientX509Util.java
new file mode 100644
index 0000000..63dd17a
--- /dev/null
+++ b/src/java/main/org/apache/zookeeper/common/ClientX509Util.java
@@ -0,0 +1,38 @@
+/**
+ * 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.zookeeper.common;
+
+public class ClientX509Util extends X509Util {
+
+    private final String sslAuthProviderProperty = getConfigPrefix() + "authProvider";
+
+    @Override
+    protected String getConfigPrefix() {
+        return "zookeeper.ssl.";
+    }
+
+    @Override
+    protected boolean shouldVerifyClientHostname() {
+        return false;
+    }
+
+    public String getSslAuthProviderProperty() {
+        return sslAuthProviderProperty;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/common/QuorumX509Util.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/common/QuorumX509Util.java b/src/java/main/org/apache/zookeeper/common/QuorumX509Util.java
new file mode 100644
index 0000000..abe90a5
--- /dev/null
+++ b/src/java/main/org/apache/zookeeper/common/QuorumX509Util.java
@@ -0,0 +1,32 @@
+/**
+ * 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.zookeeper.common;
+
+public class QuorumX509Util extends X509Util {
+
+    @Override
+    protected String getConfigPrefix() {
+        return "zookeeper.ssl.quorum.";
+    }
+
+    @Override
+    protected boolean shouldVerifyClientHostname() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/common/X509Util.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/common/X509Util.java b/src/java/main/org/apache/zookeeper/common/X509Util.java
index cc8662e..2112c70 100644
--- a/src/java/main/org/apache/zookeeper/common/X509Util.java
+++ b/src/java/main/org/apache/zookeeper/common/X509Util.java
@@ -18,64 +18,143 @@
 package org.apache.zookeeper.common;
 
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.CertPathTrustManagerParameters;
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedTrustManager;
 import javax.net.ssl.X509KeyManager;
 import javax.net.ssl.X509TrustManager;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.net.Socket;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyManagementException;
 import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicReference;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.apache.zookeeper.common.X509Exception.KeyManagerException;
-import static org.apache.zookeeper.common.X509Exception.SSLContextException;
-import static org.apache.zookeeper.common.X509Exception.TrustManagerException;
+import org.apache.zookeeper.common.X509Exception.KeyManagerException;
+import org.apache.zookeeper.common.X509Exception.SSLContextException;
+import org.apache.zookeeper.common.X509Exception.TrustManagerException;
 
 /**
  * Utility code for X509 handling
+ *
+ * Default cipher suites:
+ *
+ *   Performance testing done by Facebook engineers shows that on Intel x86_64 machines, Java9 performs better with
+ *   GCM and Java8 performs better with CBC, so these seem like reasonable defaults.
  */
-public class X509Util {
+public abstract class X509Util {
     private static final Logger LOG = LoggerFactory.getLogger(X509Util.class);
 
-    /**
-     * @deprecated Use {@link ZKConfig#SSL_KEYSTORE_LOCATION}
-     *             instead.
-     */
-    @Deprecated
-    public static final String SSL_KEYSTORE_LOCATION = "zookeeper.ssl.keyStore.location";
-    /**
-     * @deprecated Use {@link ZKConfig#SSL_KEYSTORE_PASSWD}
-     *             instead.
-     */
-    @Deprecated
-    public static final String SSL_KEYSTORE_PASSWD = "zookeeper.ssl.keyStore.password";
-    /**
-     * @deprecated Use {@link ZKConfig#SSL_TRUSTSTORE_LOCATION}
-     *             instead.
-     */
-    @Deprecated
-    public static final String SSL_TRUSTSTORE_LOCATION = "zookeeper.ssl.trustStore.location";
-    /**
-     * @deprecated Use {@link ZKConfig#SSL_TRUSTSTORE_PASSWD}
-     *             instead.
-     */
-    @Deprecated
-    public static final String SSL_TRUSTSTORE_PASSWD = "zookeeper.ssl.trustStore.password";
-    /**
-     * @deprecated Use {@link ZKConfig#SSL_AUTHPROVIDER}
-     *             instead.
-     */
-    @Deprecated
-    public static final String SSL_AUTHPROVIDER = "zookeeper.ssl.authProvider";
-
-    public static SSLContext createSSLContext() throws SSLContextException {
-        /**
+    static final String DEFAULT_PROTOCOL = "TLSv1.2";
+    private static final String[] DEFAULT_CIPHERS_JAVA8 = {
+            "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
+            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
+            "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
+            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
+    };
+    private static final String[] DEFAULT_CIPHERS_JAVA9 = {
+            "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
+            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+            "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
+            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
+    };
+
+    private String sslProtocolProperty = getConfigPrefix() + "protocol";
+    private String cipherSuitesProperty = getConfigPrefix() + "ciphersuites";
+    private String sslKeystoreLocationProperty = getConfigPrefix() + "keyStore.location";
+    private String sslKeystorePasswdProperty = getConfigPrefix() + "keyStore.password";
+    private String sslTruststoreLocationProperty = getConfigPrefix() + "trustStore.location";
+    private String sslTruststorePasswdProperty = getConfigPrefix() + "trustStore.password";
+    private String sslHostnameVerificationEnabledProperty = getConfigPrefix() + "hostnameVerification";
+    private String sslCrlEnabledProperty = getConfigPrefix() + "crl";
+    private String sslOcspEnabledProperty = getConfigPrefix() + "ocsp";
+
+    private String[] cipherSuites;
+
+    private AtomicReference<SSLContext> defaultSSLContext = new AtomicReference<>(null);
+
+    public X509Util() {
+        String cipherSuitesInput = System.getProperty(cipherSuitesProperty);
+        if (cipherSuitesInput == null) {
+            cipherSuites = getDefaultCipherSuites();
+        } else {
+            cipherSuites = cipherSuitesInput.split(",");
+        }
+    }
+
+    protected abstract String getConfigPrefix();
+    protected abstract boolean shouldVerifyClientHostname();
+
+    public String getSslProtocolProperty() {
+        return sslProtocolProperty;
+    }
+
+    public String getCipherSuitesProperty() {
+        return cipherSuitesProperty;
+    }
+
+    public String getSslKeystoreLocationProperty() {
+        return sslKeystoreLocationProperty;
+    }
+
+    public String getSslKeystorePasswdProperty() {
+        return sslKeystorePasswdProperty;
+    }
+
+    public String getSslTruststoreLocationProperty() {
+        return sslTruststoreLocationProperty;
+    }
+
+    public String getSslTruststorePasswdProperty() {
+        return sslTruststorePasswdProperty;
+    }
+
+    public String getSslHostnameVerificationEnabledProperty() {
+        return sslHostnameVerificationEnabledProperty;
+    }
+
+    public String getSslCrlEnabledProperty() {
+        return sslCrlEnabledProperty;
+    }
+
+    public String getSslOcspEnabledProperty() {
+        return sslOcspEnabledProperty;
+    }
+
+    public SSLContext getDefaultSSLContext() throws X509Exception.SSLContextException {
+        SSLContext result = defaultSSLContext.get();
+        if (result == null) {
+            result = createSSLContext();
+            if (!defaultSSLContext.compareAndSet(null, result)) {
+                // lost the race, another thread already set the value
+                result = defaultSSLContext.get();
+            }
+        }
+        return result;
+    }
+
+    private SSLContext createSSLContext() throws SSLContextException {
+        /*
          * Since Configuration initializes the key store and trust store related
          * configuration from system property. Reading property from
          * configuration will be same reading from system property
@@ -84,61 +163,62 @@ public class X509Util {
         return createSSLContext(config);
     }
 
-    public static SSLContext createSSLContext(ZKConfig config) throws SSLContextException {
+    public SSLContext createSSLContext(ZKConfig config) throws SSLContextException {
         KeyManager[] keyManagers = null;
         TrustManager[] trustManagers = null;
 
-        String keyStoreLocationProp = config.getProperty(ZKConfig.SSL_KEYSTORE_LOCATION);
-        String keyStorePasswordProp = config.getProperty(ZKConfig.SSL_KEYSTORE_PASSWD);
+        String keyStoreLocationProp = config.getProperty(sslKeystoreLocationProperty);
+        String keyStorePasswordProp = config.getProperty(sslKeystorePasswdProperty);
 
         // There are legal states in some use cases for null KeyManager or TrustManager.
         // But if a user wanna specify one, location and password are required.
 
         if (keyStoreLocationProp == null && keyStorePasswordProp == null) {
-            LOG.warn("keystore not specified for client connection");
+            LOG.warn(getSslKeystoreLocationProperty() + " not specified");
         } else {
             if (keyStoreLocationProp == null) {
-                throw new SSLContextException("keystore location not specified for client connection");
+                throw new SSLContextException(getSslKeystoreLocationProperty() + " not specified");
             }
             if (keyStorePasswordProp == null) {
-                throw new SSLContextException("keystore password not specified for client connection");
+                throw new SSLContextException(getSslKeystorePasswdProperty() + " not specified");
             }
             try {
                 keyManagers = new KeyManager[]{
                         createKeyManager(keyStoreLocationProp, keyStorePasswordProp)};
-            } catch (KeyManagerException e) {
-                throw new SSLContextException("Failed to create KeyManager", e);
+            } catch (KeyManagerException keyManagerException) {
+                throw new SSLContextException("Failed to create KeyManager", keyManagerException);
             }
         }
 
-        String trustStoreLocationProp = config.getProperty(ZKConfig.SSL_TRUSTSTORE_LOCATION);
-        String trustStorePasswordProp = config.getProperty(ZKConfig.SSL_TRUSTSTORE_PASSWD);
+        String trustStoreLocationProp = config.getProperty(sslTruststoreLocationProperty);
+        String trustStorePasswordProp = config.getProperty(sslTruststorePasswdProperty);
 
-        if (trustStoreLocationProp == null && trustStorePasswordProp == null) {
-            LOG.warn("Truststore not specified for client connection");
+        boolean sslCrlEnabled = config.getBoolean(this.sslCrlEnabledProperty);
+        boolean sslOcspEnabled = config.getBoolean(this.sslOcspEnabledProperty);
+        boolean sslServerHostnameVerificationEnabled =
+                config.getBoolean(this.getSslHostnameVerificationEnabledProperty(),true);
+        boolean sslClientHostnameVerificationEnabled = sslServerHostnameVerificationEnabled && shouldVerifyClientHostname();
+
+        if (trustStoreLocationProp == null) {
+            LOG.warn(getSslTruststoreLocationProperty() + " not specified");
         } else {
-            if (trustStoreLocationProp == null) {
-                throw new SSLContextException("Truststore location not specified for client connection");
-            }
-            if (trustStorePasswordProp == null) {
-                throw new SSLContextException("Truststore password not specified for client connection");
-            }
             try {
                 trustManagers = new TrustManager[]{
-                        createTrustManager(trustStoreLocationProp, trustStorePasswordProp)};
-            } catch (TrustManagerException e) {
-                throw new SSLContextException("Failed to create TrustManager", e);
+                        createTrustManager(trustStoreLocationProp, trustStorePasswordProp, sslCrlEnabled, sslOcspEnabled,
+                                sslServerHostnameVerificationEnabled, sslClientHostnameVerificationEnabled)};
+            } catch (TrustManagerException trustManagerException) {
+                throw new SSLContextException("Failed to create TrustManager", trustManagerException);
             }
         }
 
-        SSLContext sslContext = null;
+        String protocol = System.getProperty(sslProtocolProperty, DEFAULT_PROTOCOL);
         try {
-            sslContext = SSLContext.getInstance("TLSv1");
+            SSLContext sslContext = SSLContext.getInstance(protocol);
             sslContext.init(keyManagers, trustManagers, null);
-        } catch (Exception e) {
-            throw new SSLContextException(e);
+            return sslContext;
+        } catch (NoSuchAlgorithmException|KeyManagementException sslContextInitException) {
+            throw new SSLContextException(sslContextInitException);
         }
-        return sslContext;
     }
 
     public static X509KeyManager createKeyManager(String keyStoreLocation, String keyStorePassword)
@@ -150,7 +230,7 @@ public class X509Util {
             KeyStore ks = KeyStore.getInstance("JKS");
             inputStream = new FileInputStream(keyStoreFile);
             ks.load(inputStream, keyStorePasswordChars);
-            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
             kmf.init(ks, keyStorePasswordChars);
 
             for (KeyManager km : kmf.getKeyManagers()) {
@@ -160,43 +240,124 @@ public class X509Util {
             }
             throw new KeyManagerException("Couldn't find X509KeyManager");
 
-        } catch (Exception e) {
-            throw new KeyManagerException(e);
+        } catch (IOException|CertificateException|UnrecoverableKeyException|NoSuchAlgorithmException|KeyStoreException
+                keyManagerCreationException) {
+            throw new KeyManagerException(keyManagerCreationException);
         } finally {
             if (inputStream != null) {
                 try {
                     inputStream.close();
-                } catch (IOException e) {}
+                } catch (IOException ioException) {
+                    LOG.info("Failed to close key store input stream", ioException);
+                }
             }
         }
     }
 
-    public static X509TrustManager createTrustManager(String trustStoreLocation, String trustStorePassword)
+    public static X509TrustManager createTrustManager(String trustStoreLocation, String trustStorePassword,
+                                                      boolean crlEnabled, boolean ocspEnabled,
+                                                      final boolean serverHostnameVerificationEnabled,
+                                                      final boolean clientHostnameVerificationEnabled)
             throws TrustManagerException {
         FileInputStream inputStream = null;
         try {
-            char[] trustStorePasswordChars = trustStorePassword.toCharArray();
             File trustStoreFile = new File(trustStoreLocation);
             KeyStore ts = KeyStore.getInstance("JKS");
             inputStream = new FileInputStream(trustStoreFile);
-            ts.load(inputStream, trustStorePasswordChars);
-            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
-            tmf.init(ts);
+            if (trustStorePassword != null) {
+                char[] trustStorePasswordChars = trustStorePassword.toCharArray();
+                ts.load(inputStream, trustStorePasswordChars);
+            } else {
+                ts.load(inputStream, null);
+            }
+
+            PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new X509CertSelector());
+            if (crlEnabled || ocspEnabled) {
+                pbParams.setRevocationEnabled(true);
+                System.setProperty("com.sun.net.ssl.checkRevocation", "true");
+                System.setProperty("com.sun.security.enableCRLDP", "true");
+                if (ocspEnabled) {
+                    Security.setProperty("ocsp.enable", "true");
+                }
+            } else {
+                pbParams.setRevocationEnabled(false);
+            }
 
-            for (TrustManager tm : tmf.getTrustManagers()) {
-                if (tm instanceof X509TrustManager) {
-                    return (X509TrustManager) tm;
+            // Revocation checking is only supported with the PKIX algorithm
+            TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
+            tmf.init(new CertPathTrustManagerParameters(pbParams));
+
+            for (final TrustManager tm : tmf.getTrustManagers()) {
+                if (tm instanceof X509ExtendedTrustManager) {
+                    return new ZKTrustManager((X509ExtendedTrustManager) tm,
+                            serverHostnameVerificationEnabled, clientHostnameVerificationEnabled);
                 }
             }
             throw new TrustManagerException("Couldn't find X509TrustManager");
-        } catch (Exception e) {
-            throw new TrustManagerException(e);
+        } catch (IOException|CertificateException|NoSuchAlgorithmException|InvalidAlgorithmParameterException|KeyStoreException
+                 trustManagerCreationException) {
+            throw new TrustManagerException(trustManagerCreationException);
         } finally {
             if (inputStream != null) {
                 try {
                     inputStream.close();
-                } catch (IOException e) {}
+                } catch (IOException ioException) {
+                    LOG.info("failed to close TrustStore input stream", ioException);
+                }
             }
         }
     }
-}
\ No newline at end of file
+
+    public SSLSocket createSSLSocket() throws X509Exception, IOException {
+        SSLSocket sslSocket = (SSLSocket) getDefaultSSLContext().getSocketFactory().createSocket();
+        configureSSLSocket(sslSocket);
+
+        return sslSocket;
+    }
+
+    public SSLSocket createSSLSocket(Socket socket) throws X509Exception, IOException {
+        SSLSocket sslSocket = (SSLSocket) getDefaultSSLContext().getSocketFactory().createSocket(socket, null, socket.getPort(), true);
+        configureSSLSocket(sslSocket);
+
+        return sslSocket;
+    }
+
+    private void configureSSLSocket(SSLSocket sslSocket) {
+        SSLParameters sslParameters = sslSocket.getSSLParameters();
+        LOG.debug("Setup cipher suites for client socket: {}", Arrays.toString(cipherSuites));
+        sslParameters.setCipherSuites(cipherSuites);
+        sslSocket.setSSLParameters(sslParameters);
+    }
+
+    public SSLServerSocket createSSLServerSocket() throws X509Exception, IOException {
+        SSLServerSocket sslServerSocket = (SSLServerSocket) getDefaultSSLContext().getServerSocketFactory().createServerSocket();
+        configureSSLServerSocket(sslServerSocket);
+
+        return sslServerSocket;
+    }
+
+    public SSLServerSocket createSSLServerSocket(int port) throws X509Exception, IOException {
+        SSLServerSocket sslServerSocket = (SSLServerSocket) getDefaultSSLContext().getServerSocketFactory().createServerSocket(port);
+        configureSSLServerSocket(sslServerSocket);
+
+        return sslServerSocket;
+    }
+
+    private void configureSSLServerSocket(SSLServerSocket sslServerSocket) {
+        SSLParameters sslParameters = sslServerSocket.getSSLParameters();
+        sslParameters.setNeedClientAuth(true);
+        LOG.debug("Setup cipher suites for server socket: {}", Arrays.toString(cipherSuites));
+        sslParameters.setCipherSuites(cipherSuites);
+        sslServerSocket.setSSLParameters(sslParameters);
+    }
+
+    private String[] getDefaultCipherSuites() {
+        String javaVersion = System.getProperty("java.specification.version");
+        if ("9".equals(javaVersion)) {
+            LOG.debug("Using Java9-optimized cipher suites for Java version {}", javaVersion);
+            return DEFAULT_CIPHERS_JAVA9;
+        }
+        LOG.debug("Using Java8-optimized cipher suites for Java version {}", javaVersion);
+        return DEFAULT_CIPHERS_JAVA8;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/common/ZKConfig.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/common/ZKConfig.java b/src/java/main/org/apache/zookeeper/common/ZKConfig.java
index 8d9c001..dc24b19 100644
--- a/src/java/main/org/apache/zookeeper/common/ZKConfig.java
+++ b/src/java/main/org/apache/zookeeper/common/ZKConfig.java
@@ -42,17 +42,9 @@ import org.slf4j.LoggerFactory;
 public class ZKConfig {
 
     private static final Logger LOG = LoggerFactory.getLogger(ZKConfig.class);
-    @SuppressWarnings("deprecation")
-    public static final String SSL_KEYSTORE_LOCATION = X509Util.SSL_KEYSTORE_LOCATION;
-    @SuppressWarnings("deprecation")
-    public static final String SSL_KEYSTORE_PASSWD = X509Util.SSL_KEYSTORE_PASSWD;
-    @SuppressWarnings("deprecation")
-    public static final String SSL_TRUSTSTORE_LOCATION = X509Util.SSL_TRUSTSTORE_LOCATION;
-    @SuppressWarnings("deprecation")
-    public static final String SSL_TRUSTSTORE_PASSWD = X509Util.SSL_TRUSTSTORE_PASSWD;
-    @SuppressWarnings("deprecation")
-    public static final String SSL_AUTHPROVIDER = X509Util.SSL_AUTHPROVIDER;
+
     public static final String JUTE_MAXBUFFER = "jute.maxbuffer";
+
     /**
      * Path to a kinit binary: {@value}. Defaults to
      * <code>"/usr/bin/kinit"</code>
@@ -107,14 +99,33 @@ public class ZKConfig {
      * this configuration.
      */
     protected void handleBackwardCompatibility() {
-        properties.put(SSL_KEYSTORE_LOCATION, System.getProperty(SSL_KEYSTORE_LOCATION));
-        properties.put(SSL_KEYSTORE_PASSWD, System.getProperty(SSL_KEYSTORE_PASSWD));
-        properties.put(SSL_TRUSTSTORE_LOCATION, System.getProperty(SSL_TRUSTSTORE_LOCATION));
-        properties.put(SSL_TRUSTSTORE_PASSWD, System.getProperty(SSL_TRUSTSTORE_PASSWD));
-        properties.put(SSL_AUTHPROVIDER, System.getProperty(SSL_AUTHPROVIDER));
         properties.put(JUTE_MAXBUFFER, System.getProperty(JUTE_MAXBUFFER));
         properties.put(KINIT_COMMAND, System.getProperty(KINIT_COMMAND));
         properties.put(JGSS_NATIVE, System.getProperty(JGSS_NATIVE));
+
+        ClientX509Util clientX509Util = new ClientX509Util();
+        putSSLProperties(clientX509Util);
+        properties.put(clientX509Util.getSslAuthProviderProperty(),
+                System.getProperty(clientX509Util.getSslAuthProviderProperty()));
+
+        putSSLProperties(new QuorumX509Util());
+    }
+    
+    private void putSSLProperties(X509Util x509Util) {
+        properties.put(x509Util.getSslKeystoreLocationProperty(),
+                System.getProperty(x509Util.getSslKeystoreLocationProperty()));
+        properties.put(x509Util.getSslKeystorePasswdProperty(),
+                System.getProperty(x509Util.getSslKeystorePasswdProperty()));
+        properties.put(x509Util.getSslTruststoreLocationProperty(),
+                System.getProperty(x509Util.getSslTruststoreLocationProperty()));
+        properties.put(x509Util.getSslTruststorePasswdProperty(),
+                System.getProperty(x509Util.getSslTruststorePasswdProperty()));
+        properties.put(x509Util.getSslHostnameVerificationEnabledProperty(),
+                System.getProperty(x509Util.getSslHostnameVerificationEnabledProperty()));
+        properties.put(x509Util.getSslCrlEnabledProperty(),
+                System.getProperty(x509Util.getSslCrlEnabledProperty()));
+        properties.put(x509Util.getSslOcspEnabledProperty(),
+                System.getProperty(x509Util.getSslOcspEnabledProperty()));
     }
 
     /**
@@ -220,7 +231,29 @@ public class ZKConfig {
      * exists and is equal to the string {@code "true"}.
      */
     public boolean getBoolean(String key) {
-        return Boolean.parseBoolean(getProperty(key));
+        return getBoolean(key, false);
+    }
+
+    /**
+     * Get the value of the <code>key</code> property as a <code>boolean</code>. Returns
+     * {@code true} if and only if the property named by the argument exists and is equal
+     * to the string {@code "true"}. If the property is not set, the provided
+     * <code>defaultValue</code> is returned.
+     *
+     * @param key
+     *            property key.
+     * @param defaultValue
+     *            default value.
+     * @return return property value as an <code>boolean</code>, or
+     *         <code>defaultValue</code>
+     */
+    public boolean getBoolean(String key, boolean defaultValue) {
+        String propertyValue = getProperty(key);
+        if (propertyValue == null) {
+            return defaultValue;
+        } else {
+            return Boolean.parseBoolean(propertyValue);
+        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/common/ZKHostnameVerifier.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/common/ZKHostnameVerifier.java b/src/java/main/org/apache/zookeeper/common/ZKHostnameVerifier.java
new file mode 100644
index 0000000..740fef0
--- /dev/null
+++ b/src/java/main/org/apache/zookeeper/common/ZKHostnameVerifier.java
@@ -0,0 +1,349 @@
+/**
+ * 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.zookeeper.common;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.security.auth.x500.X500Principal;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Note: copied from Apache httpclient with some modifications. We want host verification, but depending
+ * on the httpclient jar caused unexplained performance regressions (even when the code was not used).
+ */
+final class ZKHostnameVerifier implements HostnameVerifier {
+
+    /**
+     * Note: copied from Apache httpclient with some minor modifications. We want host verification, but depending
+     * on the httpclient jar caused unexplained performance regressions (even when the code was not used).
+     */
+    private static final class SubjectName {
+        static final int DNS = 2;
+        static final int IP = 7;
+
+        private final String value;
+        private final int type;
+
+        static SubjectName IP(final String value) {
+            return new SubjectName(value, IP);
+        }
+
+        static SubjectName DNS(final String value) {
+            return new SubjectName(value, DNS);
+        }
+
+        SubjectName(final String value, final int type) {
+            if (type != DNS && type != IP) {
+                throw new IllegalArgumentException("Invalid type: " + type);
+            }
+            this.value = Objects.requireNonNull(value);
+            this.type = type;
+        }
+
+        public int getType() {
+            return type;
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
+    /**
+     * Note: copied from Apache httpclient. We want host verification, but depending on the
+     * httpclient jar caused unexplained performance regressions (even when the code was not used).
+     */
+    private static class InetAddressUtils {
+        private InetAddressUtils() {}
+
+        private static final Pattern IPV4_PATTERN = Pattern.compile(
+                "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
+
+        private static final Pattern IPV6_STD_PATTERN = Pattern.compile(
+                "^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
+
+        private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile(
+                "^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$");
+
+        static boolean isIPv4Address(final String input) {
+            return IPV4_PATTERN.matcher(input).matches();
+        }
+
+        static boolean isIPv6StdAddress(final String input) {
+            return IPV6_STD_PATTERN.matcher(input).matches();
+        }
+
+        static boolean isIPv6HexCompressedAddress(final String input) {
+            return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches();
+        }
+
+        static boolean isIPv6Address(final String input) {
+            return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input);
+        }
+    }
+
+    enum HostNameType {
+
+        IPv4(7), IPv6(7), DNS(2);
+
+        final int subjectType;
+
+        HostNameType(final int subjectType) {
+            this.subjectType = subjectType;
+        }
+
+    }
+
+    private final Logger log = LoggerFactory.getLogger(ZKHostnameVerifier.class);
+
+    @Override
+    public boolean verify(final String host, final SSLSession session) {
+        try {
+            final Certificate[] certs = session.getPeerCertificates();
+            final X509Certificate x509 = (X509Certificate) certs[0];
+            verify(host, x509);
+            return true;
+        } catch (final SSLException ex) {
+            if (log.isDebugEnabled()) {
+                log.debug(ex.getMessage(), ex);
+            }
+            return false;
+        }
+    }
+
+    void verify(final String host, final X509Certificate cert) throws SSLException {
+        final HostNameType hostType = determineHostFormat(host);
+        final List<SubjectName> subjectAlts = getSubjectAltNames(cert);
+        if (subjectAlts != null && !subjectAlts.isEmpty()) {
+            switch (hostType) {
+                case IPv4:
+                    matchIPAddress(host, subjectAlts);
+                    break;
+                case IPv6:
+                    matchIPv6Address(host, subjectAlts);
+                    break;
+                default:
+                    matchDNSName(host, subjectAlts);
+            }
+        } else {
+            // CN matching has been deprecated by rfc2818 and can be used
+            // as fallback only when no subjectAlts are available
+            final X500Principal subjectPrincipal = cert.getSubjectX500Principal();
+            final String cn = extractCN(subjectPrincipal.getName(X500Principal.RFC2253));
+            if (cn == null) {
+                throw new SSLException("Certificate subject for <" + host + "> doesn't contain " +
+                        "a common name and does not have alternative names");
+            }
+            matchCN(host, cn);
+        }
+    }
+
+    private static void matchIPAddress(final String host, final List<SubjectName> subjectAlts) throws SSLException {
+        for (int i = 0; i < subjectAlts.size(); i++) {
+            final SubjectName subjectAlt = subjectAlts.get(i);
+            if (subjectAlt.getType() == SubjectName.IP) {
+                if (host.equals(subjectAlt.getValue())) {
+                    return;
+                }
+            }
+        }
+        throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match any " +
+                "of the subject alternative names: " + subjectAlts);
+    }
+
+    private static void matchIPv6Address(final String host, final List<SubjectName> subjectAlts) throws SSLException {
+        final String normalisedHost = normaliseAddress(host);
+        for (int i = 0; i < subjectAlts.size(); i++) {
+            final SubjectName subjectAlt = subjectAlts.get(i);
+            if (subjectAlt.getType() == SubjectName.IP) {
+                final String normalizedSubjectAlt = normaliseAddress(subjectAlt.getValue());
+                if (normalisedHost.equals(normalizedSubjectAlt)) {
+                    return;
+                }
+            }
+        }
+        throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match any " +
+                "of the subject alternative names: " + subjectAlts);
+    }
+
+    private static void matchDNSName(final String host, final List<SubjectName> subjectAlts) throws SSLException {
+        final String normalizedHost = host.toLowerCase(Locale.ROOT);
+        for (int i = 0; i < subjectAlts.size(); i++) {
+            final SubjectName subjectAlt = subjectAlts.get(i);
+            if (subjectAlt.getType() == SubjectName.DNS) {
+                final String normalizedSubjectAlt = subjectAlt.getValue().toLowerCase(Locale.ROOT);
+                if (matchIdentityStrict(normalizedHost, normalizedSubjectAlt)) {
+                    return;
+                }
+            }
+        }
+        throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match any " +
+                "of the subject alternative names: " + subjectAlts);
+    }
+
+    private static void matchCN(final String host, final String cn) throws SSLException {
+        final String normalizedHost = host.toLowerCase(Locale.ROOT);
+        final String normalizedCn = cn.toLowerCase(Locale.ROOT);
+        if (!matchIdentityStrict(normalizedHost, normalizedCn)) {
+            throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match " +
+                    "common name of the certificate subject: " + cn);
+        }
+    }
+
+    private static boolean matchIdentity(final String host, final String identity,
+                                         final boolean strict) {
+        // RFC 2818, 3.1. Server Identity
+        // "...Names may contain the wildcard
+        // character * which is considered to match any single domain name
+        // component or component fragment..."
+        // Based on this statement presuming only singular wildcard is legal
+        final int asteriskIdx = identity.indexOf('*');
+        if (asteriskIdx != -1) {
+            final String prefix = identity.substring(0, asteriskIdx);
+            final String suffix = identity.substring(asteriskIdx + 1);
+            if (!prefix.isEmpty() && !host.startsWith(prefix)) {
+                return false;
+            }
+            if (!suffix.isEmpty() && !host.endsWith(suffix)) {
+                return false;
+            }
+            // Additional sanity checks on content selected by wildcard can be done here
+            if (strict) {
+                final String remainder = host.substring(
+                        prefix.length(), host.length() - suffix.length());
+                if (remainder.contains(".")) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return host.equalsIgnoreCase(identity);
+    }
+
+    private static boolean matchIdentityStrict(final String host, final String identity) {
+        return matchIdentity(host, identity, true);
+    }
+
+    private static String extractCN(final String subjectPrincipal) throws SSLException {
+        if (subjectPrincipal == null) {
+            return null;
+        }
+        try {
+            final LdapName subjectDN = new LdapName(subjectPrincipal);
+            final List<Rdn> rdns = subjectDN.getRdns();
+            for (int i = rdns.size() - 1; i >= 0; i--) {
+                final Rdn rds = rdns.get(i);
+                final Attributes attributes = rds.toAttributes();
+                final Attribute cn = attributes.get("cn");
+                if (cn != null) {
+                    try {
+                        final Object value = cn.get();
+                        if (value != null) {
+                            return value.toString();
+                        }
+                    } catch (final NoSuchElementException ignore) {
+                        // ignore exception
+                    } catch (final NamingException ignore) {
+                        // ignore exception
+                    }
+                }
+            }
+            return null;
+        } catch (final InvalidNameException e) {
+            throw new SSLException(subjectPrincipal + " is not a valid X500 distinguished name");
+        }
+    }
+
+    private static HostNameType determineHostFormat(final String host) {
+        if (InetAddressUtils.isIPv4Address(host)) {
+            return HostNameType.IPv4;
+        }
+        String s = host;
+        if (s.startsWith("[") && s.endsWith("]")) {
+            s = host.substring(1, host.length() - 1);
+        }
+        if (InetAddressUtils.isIPv6Address(s)) {
+            return HostNameType.IPv6;
+        }
+        return HostNameType.DNS;
+    }
+
+    private static List<SubjectName> getSubjectAltNames(final X509Certificate cert) {
+        try {
+            final Collection<List<?>> entries = cert.getSubjectAlternativeNames();
+            if (entries == null) {
+                return Collections.emptyList();
+            }
+            final List<SubjectName> result = new ArrayList<SubjectName>();
+            for (List<?> entry: entries) {
+                final Integer type = entry.size() >= 2 ? (Integer) entry.get(0) : null;
+                if (type != null) {
+                    final String s = (String) entry.get(1);
+                    result.add(new SubjectName(s, type));
+                }
+            }
+            return result;
+        } catch (final CertificateParsingException ignore) {
+            return Collections.emptyList();
+        }
+    }
+
+    /*
+     * Normalize IPv6 or DNS name.
+     */
+    private static String normaliseAddress(final String hostname) {
+        if (hostname == null) {
+            return hostname;
+        }
+        try {
+            final InetAddress inetAddress = InetAddress.getByName(hostname);
+            return inetAddress.getHostAddress();
+        } catch (final UnknownHostException unexpected) { // Should not happen, because we check for IPv6 address above
+            return hostname;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/common/ZKTrustManager.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/common/ZKTrustManager.java b/src/java/main/org/apache/zookeeper/common/ZKTrustManager.java
new file mode 100644
index 0000000..73006d0
--- /dev/null
+++ b/src/java/main/org/apache/zookeeper/common/ZKTrustManager.java
@@ -0,0 +1,150 @@
+/**
+ * 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.zookeeper.common;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.X509ExtendedTrustManager;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+/**
+ * A custom TrustManager that supports hostname verification via org.apache.http.conn.ssl.DefaultHostnameVerifier.
+ *
+ * We attempt to perform verification using just the IP address first and if that fails will attempt to perform a
+ * reverse DNS lookup and verify using the hostname.
+ */
+public class ZKTrustManager extends X509ExtendedTrustManager {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ZKTrustManager.class);
+
+    private X509ExtendedTrustManager x509ExtendedTrustManager;
+    private boolean serverHostnameVerificationEnabled;
+    private boolean clientHostnameVerificationEnabled;
+
+    private ZKHostnameVerifier hostnameVerifier;
+
+    /**
+     * Instantiate a new ZKTrustManager.
+     *
+     * @param x509ExtendedTrustManager The trustmanager to use for checkClientTrusted/checkServerTrusted logic
+     * @param serverHostnameVerificationEnabled  If true, this TrustManager should verify hostnames of servers that this
+     *                                 instance connects to.
+     * @param clientHostnameVerificationEnabled  If true, the hostname of a client connecting to this machine will be
+     *                                           verified.
+     */
+    ZKTrustManager(X509ExtendedTrustManager x509ExtendedTrustManager, boolean serverHostnameVerificationEnabled,
+                   boolean clientHostnameVerificationEnabled) {
+        this.x509ExtendedTrustManager = x509ExtendedTrustManager;
+        this.serverHostnameVerificationEnabled = serverHostnameVerificationEnabled;
+        this.clientHostnameVerificationEnabled = clientHostnameVerificationEnabled;
+        hostnameVerifier = new ZKHostnameVerifier();
+    }
+
+    @Override
+    public X509Certificate[] getAcceptedIssuers() {
+        return x509ExtendedTrustManager.getAcceptedIssuers();
+    }
+
+    @Override
+    public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+        x509ExtendedTrustManager.checkClientTrusted(chain, authType, socket);
+        if (clientHostnameVerificationEnabled) {
+            performHostVerification(socket.getInetAddress(), chain[0]);
+        }
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+        x509ExtendedTrustManager.checkServerTrusted(chain, authType, socket);
+        if (serverHostnameVerificationEnabled) {
+            performHostVerification(socket.getInetAddress(), chain[0]);
+        }
+    }
+
+    @Override
+    public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
+        x509ExtendedTrustManager.checkServerTrusted(chain, authType, engine);
+        if (clientHostnameVerificationEnabled) {
+            try {
+                performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
+            } catch (UnknownHostException e) {
+                throw new CertificateException("Failed to verify host", e);
+            }
+        }
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
+            throws CertificateException {
+        x509ExtendedTrustManager.checkServerTrusted(chain, authType, engine);
+        if (serverHostnameVerificationEnabled) {
+            try {
+                performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
+            } catch (UnknownHostException e) {
+                throw new CertificateException("Failed to verify host", e);
+            }
+        }
+    }
+
+    @Override
+    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+        x509ExtendedTrustManager.checkClientTrusted(chain, authType);
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+        x509ExtendedTrustManager.checkServerTrusted(chain, authType);
+    }
+
+    /**
+     * Compares peer's hostname with the one stored in the provided client certificate. Performs verification
+     * with the help of provided HostnameVerifier.
+     *
+     * @param inetAddress Peer's inet address.
+     * @param certificate Peer's certificate
+     * @throws CertificateException Thrown if the provided certificate doesn't match the peer hostname.
+     */
+    private void performHostVerification(InetAddress inetAddress, X509Certificate certificate)
+            throws CertificateException {
+        String hostAddress = "";
+        String hostName = "";
+        try {
+            hostAddress = inetAddress.getHostAddress();
+            hostnameVerifier.verify(hostAddress, certificate);
+        } catch (SSLException addressVerificationException) {
+            try {
+                LOG.debug("Failed to verify host address: {} attempting to verify host name with reverse dns lookup",
+                        hostAddress, addressVerificationException);
+                hostName = inetAddress.getHostName();
+                hostnameVerifier.verify(hostName, certificate);
+            } catch (SSLException hostnameVerificationException) {
+                LOG.error("Failed to verify host address: {}", hostAddress, addressVerificationException);
+                LOG.error("Failed to verify hostname: {}", hostName, hostnameVerificationException);
+                throw new CertificateException("Failed to verify both host address and host name",
+                        hostnameVerificationException);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java b/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java
index a024689..67e0fba 100644
--- a/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java
+++ b/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java
@@ -18,31 +18,10 @@
 
 package org.apache.zookeeper.server;
 
-import static org.jboss.netty.buffer.ChannelBuffers.dynamicBuffer;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executors;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.X509KeyManager;
-import javax.net.ssl.X509TrustManager;
-
 import org.apache.zookeeper.KeeperException;
-import org.apache.zookeeper.common.ZKConfig;
+import org.apache.zookeeper.common.ClientX509Util;
 import org.apache.zookeeper.common.X509Exception;
 import org.apache.zookeeper.common.X509Exception.SSLContextException;
-import org.apache.zookeeper.common.X509Util;
 import org.apache.zookeeper.server.auth.ProviderRegistry;
 import org.apache.zookeeper.server.auth.X509AuthenticationProvider;
 import org.jboss.netty.bootstrap.ServerBootstrap;
@@ -68,6 +47,25 @@ import org.jboss.netty.handler.ssl.SslHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executors;
+
+import static org.jboss.netty.buffer.ChannelBuffers.dynamicBuffer;
+
 public class NettyServerCnxnFactory extends ServerCnxnFactory {
     private static final Logger LOG = LoggerFactory.getLogger(NettyServerCnxnFactory.class);
 
@@ -78,6 +76,7 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory {
         new HashMap<InetAddress, Set<NettyServerCnxn>>( );
     InetSocketAddress localAddress;
     int maxClientCnxns = 60;
+    ClientX509Util x509Util;
 
     /**
      * This is an inner class since we need to extend SimpleChannelHandler, but
@@ -292,7 +291,7 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory {
                     cnxn.setClientCertificateChain(session.getPeerCertificates());
 
                     String authProviderProp
-                            = System.getProperty(ZKConfig.SSL_AUTHPROVIDER, "x509");
+                            = System.getProperty(x509Util.getSslAuthProviderProperty(), "x509");
 
                     X509AuthenticationProvider authProvider =
                             (X509AuthenticationProvider)
@@ -348,20 +347,20 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory {
                 return p;
             }
         });
+        x509Util = new ClientX509Util();
     }
 
     private synchronized void initSSL(ChannelPipeline p)
             throws X509Exception, KeyManagementException, NoSuchAlgorithmException {
-        String authProviderProp = System.getProperty(ZKConfig.SSL_AUTHPROVIDER);
+        String authProviderProp = System.getProperty(x509Util.getSslAuthProviderProperty());
         SSLContext sslContext;
         if (authProviderProp == null) {
-            sslContext = X509Util.createSSLContext();
+            sslContext = x509Util.getDefaultSSLContext();
         } else {
             sslContext = SSLContext.getInstance("TLSv1");
             X509AuthenticationProvider authProvider =
                     (X509AuthenticationProvider)ProviderRegistry.getProvider(
-                            System.getProperty(ZKConfig.SSL_AUTHPROVIDER,
-                                    "x509"));
+                            System.getProperty(x509Util.getSslAuthProviderProperty(), "x509"));
 
             if (authProvider == null)
             {

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java b/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
index 93bc8fc..8a699ce 100644
--- a/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
+++ b/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
@@ -26,6 +26,7 @@ import javax.net.ssl.X509TrustManager;
 import javax.security.auth.x500.X500Principal;
 
 import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.common.ClientX509Util;
 import org.apache.zookeeper.common.ZKConfig;
 import org.apache.zookeeper.common.X509Exception;
 import org.apache.zookeeper.common.X509Exception.KeyManagerException;
@@ -66,47 +67,50 @@ public class X509AuthenticationProvider implements AuthenticationProvider {
      * <br/><code>zookeeper.ssl.trustStore.password</code>
      */
     public X509AuthenticationProvider() throws X509Exception {
-        String keyStoreLocationProp = System.getProperty(
-                ZKConfig.SSL_KEYSTORE_LOCATION);
-        String keyStorePasswordProp = System.getProperty(
-                ZKConfig.SSL_KEYSTORE_PASSWD);
+        ZKConfig config = new ZKConfig();
+        X509Util x509Util = new ClientX509Util();
+
+        String keyStoreLocation = config.getProperty(x509Util.getSslKeystoreLocationProperty());
+        String keyStorePassword = config.getProperty(x509Util.getSslKeystorePasswdProperty());
+
+        boolean crlEnabled = Boolean.parseBoolean(System.getProperty(x509Util.getSslCrlEnabledProperty()));
+        boolean ocspEnabled = Boolean.parseBoolean(System.getProperty(x509Util.getSslOcspEnabledProperty()));
+        boolean hostnameVerificationEnabled = Boolean.parseBoolean(System.getProperty(x509Util.getSslHostnameVerificationEnabledProperty()));
 
         X509KeyManager km = null;
         X509TrustManager tm = null;
-        if (keyStoreLocationProp == null && keyStorePasswordProp == null) {
+        if (keyStoreLocation == null && keyStorePassword == null) {
             LOG.warn("keystore not specified for client connection");
         } else {
-            if (keyStoreLocationProp == null) {
+            if (keyStoreLocation == null) {
                 throw new X509Exception("keystore location not specified for client connection");
             }
-            if (keyStorePasswordProp == null) {
+            if (keyStorePassword == null) {
                 throw new X509Exception("keystore password not specified for client connection");
             }
             try {
-                km = X509Util.createKeyManager(
-                        keyStoreLocationProp, keyStorePasswordProp);
+                km = X509Util.createKeyManager(keyStoreLocation, keyStorePassword);
             } catch (KeyManagerException e) {
                 LOG.error("Failed to create key manager", e);
             }
         }
         
-        String trustStoreLocationProp = System.getProperty(
-                ZKConfig.SSL_TRUSTSTORE_LOCATION);
-        String trustStorePasswordProp = System.getProperty(
-                ZKConfig.SSL_TRUSTSTORE_PASSWD);
+        String trustStoreLocation = config.getProperty(x509Util.getSslTruststoreLocationProperty());
+        String trustStorePassword = config.getProperty(x509Util.getSslTruststorePasswdProperty());
 
-        if (trustStoreLocationProp == null && trustStorePasswordProp == null) {
+        if (trustStoreLocation == null && trustStorePassword == null) {
             LOG.warn("Truststore not specified for client connection");
         } else {
-            if (trustStoreLocationProp == null) {
+            if (trustStoreLocation == null) {
                 throw new X509Exception("Truststore location not specified for client connection");
             }
-            if (trustStorePasswordProp == null) {
+            if (trustStorePassword == null) {
                 throw new X509Exception("Truststore password not specified for client connection");
             }
             try {
                 tm = X509Util.createTrustManager(
-                        trustStoreLocationProp, trustStorePasswordProp);
+                        trustStoreLocation, trustStorePassword, crlEnabled, ocspEnabled,
+                        hostnameVerificationEnabled, false);
             } catch (TrustManagerException e) {
                 LOG.error("Failed to create trust manager", e);
             }

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/server/quorum/Leader.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/Leader.java b/src/java/main/org/apache/zookeeper/server/quorum/Leader.java
index 359d7e3..a56c0f5 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/Leader.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/Leader.java
@@ -41,7 +41,9 @@ import java.util.concurrent.atomic.AtomicLong;
 import javax.security.sasl.SaslException;
 
 import org.apache.zookeeper.ZooDefs.OpCode;
+import org.apache.zookeeper.common.QuorumX509Util;
 import org.apache.zookeeper.common.Time;
+import org.apache.zookeeper.common.X509Exception;
 import org.apache.zookeeper.server.FinalRequestProcessor;
 import org.apache.zookeeper.server.Request;
 import org.apache.zookeeper.server.RequestProcessor;
@@ -225,19 +227,36 @@ public class Leader {
     
     private final ServerSocket ss;
 
-    Leader(QuorumPeer self,LeaderZooKeeperServer zk) throws IOException {
+    Leader(QuorumPeer self,LeaderZooKeeperServer zk) throws IOException, X509Exception {
         this.self = self;
         this.proposalStats = new BufferStats();
         try {
-            if (self.getQuorumListenOnAllIPs()) {
-                ss = new ServerSocket(self.getQuorumAddress().getPort());
+            if (self.shouldUsePortUnification()) {
+                if (self.getQuorumListenOnAllIPs()) {
+                    ss = new UnifiedServerSocket(new QuorumX509Util(), self.getQuorumAddress().getPort());
+                } else {
+                    ss = new UnifiedServerSocket(new QuorumX509Util());
+                }
+            } else if (self.isSslQuorum()) {
+                if (self.getQuorumListenOnAllIPs()) {
+                    ss = new QuorumX509Util().createSSLServerSocket(self.getQuorumAddress().getPort());
+                } else {
+                    ss = new QuorumX509Util().createSSLServerSocket();
+                }
             } else {
-                ss = new ServerSocket();
+                if (self.getQuorumListenOnAllIPs()) {
+                    ss = new ServerSocket(self.getQuorumAddress().getPort());
+                } else {
+                    ss = new ServerSocket();
+                }
             }
             ss.setReuseAddress(true);
             if (!self.getQuorumListenOnAllIPs()) {
                 ss.bind(self.getQuorumAddress());
             }
+        } catch (X509Exception e) {
+            LOG.error("Failed to setup ssl server socket", e);
+            throw e;
         } catch (BindException e) {
             if (self.getQuorumListenOnAllIPs()) {
                 LOG.error("Couldn't bind to port " + self.getQuorumAddress().getPort(), e);
@@ -373,6 +392,7 @@ public class Leader {
                 while (!stop) {
                     try{
                         Socket s = ss.accept();
+
                         // start with the initLimit, once the ack is processed
                         // in LearnerHandler switch to the syncLimit
                         s.setSoTimeout(self.tickTime * self.initLimit);

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/server/quorum/Learner.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/Learner.java b/src/java/main/org/apache/zookeeper/server/quorum/Learner.java
index d4c458f..667f73c 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/Learner.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/Learner.java
@@ -38,6 +38,9 @@ import org.apache.jute.BinaryOutputArchive;
 import org.apache.jute.InputArchive;
 import org.apache.jute.OutputArchive;
 import org.apache.jute.Record;
+import org.apache.zookeeper.common.QuorumX509Util;
+import org.apache.zookeeper.common.X509Exception;
+import org.apache.zookeeper.common.X509Util;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.zookeeper.ZooDefs.OpCode;
@@ -50,8 +53,8 @@ import org.apache.zookeeper.server.util.SerializeUtils;
 import org.apache.zookeeper.server.util.ZxidUtils;
 import org.apache.zookeeper.txn.SetDataTxn;
 import org.apache.zookeeper.txn.TxnHeader;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLSocket;
 
 /**
  * This class is the superclass of two of the three main actors in a ZK
@@ -69,6 +72,8 @@ public class Learner {
     protected BufferedOutputStream bufferedOutput;
     
     protected Socket sock;
+
+    protected X509Util x509Util;
     
     /**
      * Socket getter
@@ -242,9 +247,8 @@ public class Learner {
      * @throws InterruptedException
      */
     protected void connectToLeader(InetSocketAddress addr, String hostname)
-    throws IOException, ConnectException, InterruptedException {
-        sock = new Socket();        
-        sock.setSoTimeout(self.tickTime * self.initLimit);
+            throws IOException, InterruptedException, X509Exception {
+        this.sock = createSocket();
 
         int initLimitTime = self.tickTime * self.initLimit;
         int remainingInitLimitTime = initLimitTime;
@@ -260,6 +264,9 @@ public class Learner {
                 }
 
                 sockConnect(sock, addr, Math.min(self.tickTime * self.syncLimit, remainingInitLimitTime));
+                if (self.isSslQuorum())  {
+                    ((SSLSocket) sock).startHandshake();
+                }
                 sock.setTcpNoDelay(nodelay);
                 break;
             } catch (IOException e) {
@@ -279,8 +286,7 @@ public class Learner {
                     LOG.warn("Unexpected exception, tries=" + tries +
                             ", remaining init limit=" + remainingInitLimitTime +
                             ", connecting to " + addr,e);
-                    sock = new Socket();
-                    sock.setSoTimeout(self.tickTime * self.initLimit);
+                    this.sock = createSocket();
                 }
             }
             Thread.sleep(1000);
@@ -292,8 +298,22 @@ public class Learner {
                 sock.getInputStream()));
         bufferedOutput = new BufferedOutputStream(sock.getOutputStream());
         leaderOs = BinaryOutputArchive.getArchive(bufferedOutput);
-    }   
-    
+    }
+
+    private Socket createSocket() throws X509Exception, IOException {
+        Socket sock;
+        if (self.isSslQuorum()) {
+            if (x509Util == null) {
+                x509Util = new QuorumX509Util();
+            }
+            sock = x509Util.createSSLSocket();
+        } else {
+            sock = new Socket();
+        }
+        sock.setSoTimeout(self.tickTime * self.initLimit);
+        return sock;
+    }
+
     /**
      * Once connected to the leader, perform the handshake protocol to
      * establish a following / observing connection. 

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/server/quorum/PrependableSocket.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/PrependableSocket.java b/src/java/main/org/apache/zookeeper/server/quorum/PrependableSocket.java
new file mode 100644
index 0000000..a86608f
--- /dev/null
+++ b/src/java/main/org/apache/zookeeper/server/quorum/PrependableSocket.java
@@ -0,0 +1,49 @@
+/**
+ * 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.zookeeper.server.quorum;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.net.Socket;
+import java.net.SocketImpl;
+
+public class PrependableSocket extends Socket {
+
+  private SequenceInputStream sequenceInputStream;
+
+  public PrependableSocket(SocketImpl base) throws IOException {
+    super(base);
+  }
+
+  @Override
+  public InputStream getInputStream() throws IOException {
+    if (sequenceInputStream == null) {
+      return super.getInputStream();
+    }
+
+    return sequenceInputStream;
+  }
+
+  public void prependToInputStream(byte[] bytes) throws IOException {
+    sequenceInputStream = new SequenceInputStream(new ByteArrayInputStream(bytes), getInputStream());
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/server/quorum/QuorumBean.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumBean.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumBean.java
index 0c0f112..9b0717c 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumBean.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumBean.java
@@ -29,16 +29,29 @@ public class QuorumBean implements QuorumMXBean, ZKMBeanInfo {
         this.peer = peer;
         name = "ReplicatedServer_id" + peer.getId();
     }
-    
+
+    @Override
     public String getName() {
         return name;
     }
-    
+
+    @Override
     public boolean isHidden() {
         return false;
     }
-    
+
+    @Override
     public int getQuorumSize() {
         return peer.getQuorumSize();
     }
+
+    @Override
+    public boolean isSslQuorum() {
+        return peer.isSslQuorum();
+    }
+
+    @Override
+    public boolean isPortUnification() {
+        return peer.shouldUsePortUnification();
+    }
 }

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/server/quorum/QuorumCnxManager.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumCnxManager.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumCnxManager.java
index ffc1fd1..53a9906 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumCnxManager.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumCnxManager.java
@@ -45,6 +45,9 @@ import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
+import org.apache.zookeeper.common.QuorumX509Util;
+import org.apache.zookeeper.common.X509Exception;
+import org.apache.zookeeper.common.X509Util;
 import org.apache.zookeeper.server.ZooKeeperThread;
 import org.apache.zookeeper.server.quorum.auth.QuorumAuthLearner;
 import org.apache.zookeeper.server.quorum.auth.QuorumAuthServer;
@@ -52,6 +55,8 @@ import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.net.ssl.SSLSocket;
+
 /**
  * This class implements a connection manager for leader election using TCP. It
  * maintains one connection for every pair of servers. The tricky part is to
@@ -165,6 +170,8 @@ public class QuorumCnxManager {
     private final boolean tcpKeepAlive = Boolean.getBoolean("zookeeper.tcpKeepAlive");
 
 
+    private X509Util x509Util;
+
     static public class Message {
         Message(ByteBuffer buffer, long sid) {
             this.buffer = buffer;
@@ -270,9 +277,11 @@ public class QuorumCnxManager {
         initializeAuth(mySid, authServer, authLearner, quorumCnxnThreadsSize,
                 quorumSaslAuthEnabled);
 
-        // Starts listener thread that waits for connection requests 
+        // Starts listener thread that waits for connection requests
         listener = new Listener();
         listener.setName("QuorumPeerListener");
+
+        x509Util = new QuorumX509Util();
     }
 
     private void initializeAuth(final long mySid,
@@ -534,14 +543,13 @@ public class QuorumCnxManager {
                 LOG.info("Setting arbitrary identifier to observer: " + sid);
             }
         } catch (IOException e) {
+            LOG.warn("Exception reading or writing challenge: {}", e);
             closeSocket(sock);
-            LOG.warn("Exception reading or writing challenge: {}", e.toString());
             return;
         }
 
         // do authenticating learner
         authServer.authenticate(sock, din);
-
         //If wins the challenge, then close the new connection.
         if (sid < self.getId()) {
             /*
@@ -633,9 +641,17 @@ public class QuorumCnxManager {
         Socket sock = null;
         try {
             LOG.debug("Opening channel to server " + sid);
-            sock = new Socket();
-            setSockOpts(sock);
-            sock.connect(electionAddr, cnxTO);
+            if (self.isSslQuorum()) {
+                SSLSocket sslSock = x509Util.createSSLSocket();
+                setSockOpts(sslSock);
+                sslSock.connect(electionAddr, cnxTO);
+                sslSock.startHandshake();
+                sock = sslSock;
+            } else {
+                sock = new Socket();
+                setSockOpts(sock);
+                sock.connect(electionAddr, cnxTO);
+            }
             LOG.debug("Connected to server " + sid);
             // Sends connection request asynchronously if the quorum
             // sasl authentication is enabled. This is required because
@@ -656,6 +672,11 @@ public class QuorumCnxManager {
                     + " at election address " + electionAddr, e);
             closeSocket(sock);
             throw e;
+        } catch (X509Exception e) {
+            LOG.warn("Cannot open secure channel to " + sid
+                    + " at election address " + electionAddr, e);
+            closeSocket(sock);
+            return false;
         } catch (IOException e) {
             LOG.warn("Cannot open channel to " + sid
                             + " at election address " + electionAddr,
@@ -840,8 +861,16 @@ public class QuorumCnxManager {
             Socket client = null;
             while((!shutdown) && (numRetries < 3)){
                 try {
-                    ss = new ServerSocket();
+                    if (self.shouldUsePortUnification()) {
+                        ss = new UnifiedServerSocket(x509Util);
+                    } else if (self.isSslQuorum()) {
+                        ss = x509Util.createSSLServerSocket();
+                    } else {
+                        ss = new ServerSocket();
+                    }
+
                     ss.setReuseAddress(true);
+
                     if (self.getQuorumListenOnAllIPs()) {
                         int port = self.getElectionAddress().getPort();
                         addr = new InetSocketAddress(port);
@@ -856,6 +885,7 @@ public class QuorumCnxManager {
                     ss.bind(addr);
                     while (!shutdown) {
                         client = ss.accept();
+
                         setSockOpts(client);
                         LOG.info("Received connection request "
                                 + client.getRemoteSocketAddress());
@@ -871,7 +901,7 @@ public class QuorumCnxManager {
                         }
                         numRetries = 0;
                     }
-                } catch (IOException e) {
+                } catch (IOException|X509Exception e) {
                     if (shutdown) {
                         break;
                     }

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/server/quorum/QuorumMXBean.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumMXBean.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumMXBean.java
index 2edce68..d0215c8 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumMXBean.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumMXBean.java
@@ -31,4 +31,14 @@ public interface QuorumMXBean {
      * @return configured number of peers in the quorum
      */
     public int getQuorumSize();
+
+    /**
+     * @return SSL communication between quorum members required
+     */
+    public boolean isSslQuorum();
+
+    /**
+     * @return SSL communication between quorum members enabled
+     */
+    public boolean isPortUnification();
 }

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/0e3b82bd/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
----------------------------------------------------------------------
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
index c0c8a87..0d8a012 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
@@ -49,6 +49,7 @@ import org.apache.zookeeper.KeeperException.BadArgumentsException;
 import org.apache.zookeeper.common.AtomicFileWritingIdiom;
 import org.apache.zookeeper.common.AtomicFileWritingIdiom.WriterStatement;
 import org.apache.zookeeper.common.Time;
+import org.apache.zookeeper.common.X509Exception;
 import org.apache.zookeeper.jmx.MBeanRegistry;
 import org.apache.zookeeper.jmx.ZKMBeanInfo;
 import org.apache.zookeeper.server.ServerCnxnFactory;
@@ -254,7 +255,7 @@ public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider
                 throw new ConfigException("Address unresolved: " + serverParts[0] + ":" + serverParts[1]);
             }
             try {
-                electionAddr = new InetSocketAddress(serverParts[0], 
+                electionAddr = new InetSocketAddress(serverParts[0],
                         Integer.parseInt(serverParts[2]));
             } catch (NumberFormatException e) {
                 throw new ConfigException("Address unresolved: " + serverParts[0] + ":" + serverParts[2]);
@@ -481,6 +482,17 @@ public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider
         this.myid = id;
     }
 
+    private boolean sslQuorum;
+    private boolean shouldUsePortUnification;
+
+    public boolean isSslQuorum() {
+        return sslQuorum;
+    }
+
+    public boolean shouldUsePortUnification() {
+        return shouldUsePortUnification;
+    }
+
     /**
      * This is who I think the leader currently is.
      */
@@ -1033,7 +1045,7 @@ public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider
         return new Follower(this, new FollowerZooKeeperServer(logFactory, this, this.zkDb));
     }
 
-    protected Leader makeLeader(FileTxnSnapLog logFactory) throws IOException {
+    protected Leader makeLeader(FileTxnSnapLog logFactory) throws IOException, X509Exception {
         return new Leader(this, new LeaderZooKeeperServer(logFactory, this, this.zkDb));
     }
 
@@ -1716,6 +1728,14 @@ public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider
         this.secureCnxnFactory = secureCnxnFactory;
     }
 
+    public void setSslQuorum(boolean sslQuorum) {
+        this.sslQuorum = sslQuorum;
+    }
+
+    public void setUsePortUnification(boolean shouldUsePortUnification) {
+        this.shouldUsePortUnification = shouldUsePortUnification;
+    }
+
     private void startServerCnxnFactory() {
         if (cnxnFactory != null) {
             cnxnFactory.start();


Mime
View raw message