zookeeper-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rake...@apache.org
Subject svn commit: r1669825 - in /zookeeper/trunk: ./ src/java/main/org/apache/zookeeper/ src/java/main/org/apache/zookeeper/client/ src/java/main/org/apache/zookeeper/common/ src/java/main/org/apache/zookeeper/server/ src/java/main/org/apache/zookeeper/serve...
Date Sat, 28 Mar 2015 19:08:52 GMT
Author: rakeshr
Date: Sat Mar 28 19:08:52 2015
New Revision: 1669825

URL: http://svn.apache.org/r1669825
Log:
ZOOKEEPER-2123 Provide implementation of X509 AuthenticationProvider(Ian Dimayuga via rakeshr)

Added:
    zookeeper/trunk/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
    zookeeper/trunk/src/java/test/org/apache/zookeeper/server/MockServerCnxn.java
    zookeeper/trunk/src/java/test/org/apache/zookeeper/test/SSLAuthTest.java
    zookeeper/trunk/src/java/test/org/apache/zookeeper/test/X509AuthTest.java
Modified:
    zookeeper/trunk/CHANGES.txt
    zookeeper/trunk/src/java/main/org/apache/zookeeper/ZooKeeperMain.java
    zookeeper/trunk/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java
    zookeeper/trunk/src/java/main/org/apache/zookeeper/common/X509Util.java
    zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java
    zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java
    zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java
    zookeeper/trunk/src/java/main/org/apache/zookeeper/server/ServerCnxn.java
    zookeeper/trunk/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java
    zookeeper/trunk/src/java/main/org/apache/zookeeper/server/auth/ProviderRegistry.java
    zookeeper/trunk/src/java/test/org/apache/zookeeper/test/ClientBase.java
    zookeeper/trunk/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java
    zookeeper/trunk/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java

Modified: zookeeper/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/zookeeper/trunk/CHANGES.txt?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/CHANGES.txt (original)
+++ zookeeper/trunk/CHANGES.txt Sat Mar 28 19:08:52 2015
@@ -7,6 +7,9 @@ NEW FEATURES:
 
   ZOOKEEPER-2125 SSL on Netty client-server communication (Hongchao, Ian Dimayuga via rakeshr)
 
+  ZOOKEEPER-2123 Provide implementation of X509 AuthenticationProvider
+  (Ian Dimayuga via rakeshr)
+
 BUGFIXES:
   ZOOKEEPER-1784 wrong check for COMMITANDACTIVATE in observer code, Learner.java (rgs via shralex).
 

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/ZooKeeperMain.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/ZooKeeperMain.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/ZooKeeperMain.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/ZooKeeperMain.java Sat Mar 28 19:08:52 2015
@@ -264,6 +264,10 @@ public class ZooKeeperMain {
         }
         host = newHost;
         boolean readOnly = cl.getOption("readonly") != null;
+        if (cl.getOption("secure") != null) {
+            System.setProperty(ZooKeeper.SECURE_CLIENT, "true");
+            System.out.println("Secure connection is enabled");
+        }
         zk = new ZooKeeper(host,
                  Integer.parseInt(cl.getOption("timeout")),
                  new MyWatcher(), readOnly);

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java Sat Mar 28 19:08:52 2015
@@ -18,17 +18,22 @@
 
 package org.apache.zookeeper.client;
 
-import org.apache.log4j.Logger;
-
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.Socket;
 
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.apache.log4j.Logger;
+import org.apache.zookeeper.common.X509Exception.SSLContextException;
+import org.apache.zookeeper.common.X509Util;
+
 public class FourLetterWordMain {
     protected static final Logger LOG = Logger.getLogger(FourLetterWordMain.class);
-    
     /**
      * Send the 4letterword
      * @param host the destination host
@@ -36,19 +41,50 @@ public class FourLetterWordMain {
      * @param cmd the 4letterword
      * @return server response
      * @throws java.io.IOException
+     * @throws SSLContextException
      */
     public static String send4LetterWord(String host, int port, String cmd)
-            throws IOException
-    {
+            throws IOException, SSLContextException {
+        return send4LetterWord(host, port, cmd, false);
+    }
+
+    /**
+     * Send the 4letterword
+     * @param host the destination host
+     * @param port the destination port
+     * @param cmd the 4letterword
+     * @param secure whether to use SSL
+     * @return server response
+     * @throws java.io.IOException
+     * @throws SSLContextException
+     */
+    public static String send4LetterWord(String host, int port, String cmd, boolean secure)
+            throws IOException, SSLContextException {
         LOG.info("connecting to " + host + " " + port);
-        Socket sock = new Socket(host, port);
+        Socket sock;
+
+        if (secure) {
+            LOG.info("using secure socket");
+            SSLContext sslContext = X509Util.createSSLContext();
+            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
+            SSLSocket sslSock = (SSLSocket) socketFactory.createSocket(host, port);
+            sslSock.startHandshake();
+            sock = sslSock;
+        } else {
+            sock = new Socket(host, port);
+        }
+
         BufferedReader reader = null;
         try {
             OutputStream outstream = sock.getOutputStream();
             outstream.write(cmd.getBytes());
             outstream.flush();
+
             // this replicates NC - close the output stream before reading
-            sock.shutdownOutput();
+            if (!secure) {
+                // SSL prohibits unilateral half-close
+                sock.shutdownOutput();
+            }
 
             reader =
                     new BufferedReader(
@@ -68,12 +104,14 @@ public class FourLetterWordMain {
     }
     
     public static void main(String[] args)
-            throws IOException
+            throws IOException, SSLContextException
     {
-        if (args.length != 3) {
-            System.out.println("Usage: FourLetterWordMain <host> <port> <cmd>");
-        } else {
+        if (args.length == 3) {
             System.out.println(send4LetterWord(args[0], Integer.parseInt(args[1]), args[2]));
+        } else if (args.length == 4) {
+            System.out.println(send4LetterWord(args[0], Integer.parseInt(args[1]), args[2], Boolean.parseBoolean(args[3])));
+        } else {
+            System.out.println("Usage: FourLetterWordMain <host> <port> <cmd> <secure(optional)>");
         }
     }
 }

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/common/X509Util.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/common/X509Util.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/common/X509Util.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/common/X509Util.java Sat Mar 28 19:08:52 2015
@@ -47,6 +47,7 @@ public class X509Util {
     public static final String SSL_KEYSTORE_PASSWD = "zookeeper.ssl.keyStore.password";
     public static final String SSL_TRUSTSTORE_LOCATION = "zookeeper.ssl.trustStore.location";
     public static final String SSL_TRUSTSTORE_PASSWD = "zookeeper.ssl.trustStore.password";
+    public static final String SSL_AUTHPROVIDER = "zookeeper.ssl.authProvider";
 
     public static SSLContext createSSLContext() throws SSLContextException {
         KeyManager[] keyManagers = null;

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java Sat Mar 28 19:08:52 2015
@@ -29,6 +29,7 @@ import java.nio.ByteBuffer;
 import java.nio.channels.CancelledKeyException;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.SocketChannel;
+import java.security.cert.Certificate;
 import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -1178,4 +1179,20 @@ public class NIOServerCnxn extends Serve
         return zkServer.serverStats();
     }
 
+    @Override
+    public boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    public Certificate[] getClientCertificateChain() {
+        throw new UnsupportedOperationException(
+                "SSL is unsupported in NIOServerCnxn");
+    }
+
+    @Override
+    public void setClientCertificateChain(Certificate[] chain) {
+        throw new UnsupportedOperationException(
+                "SSL is unsupported in NIOServerCnxn");
+    }
 }

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java Sat Mar 28 19:08:52 2015
@@ -28,7 +28,9 @@ import java.io.Writer;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.nio.ByteBuffer;
+import java.security.cert.Certificate;
 import java.util.AbstractSet;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -64,6 +66,7 @@ public class NettyServerCnxn extends Ser
     long sessionId;
     int sessionTimeout;
     AtomicLong outstandingCount = new AtomicLong();
+    Certificate[] clientChain;
 
     /** The ZooKeeperServer for this connection. May be null if the server
      * is not currently serving requests (for example if the server is not
@@ -853,4 +856,28 @@ public class NettyServerCnxn extends Ser
         return zkServer.serverStats();
     }
 
+    @Override
+    public boolean isSecure() {
+        return factory.secure;
+    }
+
+    @Override
+    public Certificate[] getClientCertificateChain() {
+        if (clientChain == null)
+        {
+            return null;
+        }
+        return Arrays.copyOf(clientChain, clientChain.length);
+    }
+
+    @Override
+    public void setClientCertificateChain(Certificate[] chain) {
+        if (chain == null)
+        {
+            clientChain = null;
+        } else {
+            clientChain = Arrays.copyOf(chain, chain.length);
+        }
+    }
+
 }

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java Sat Mar 28 19:08:52 2015
@@ -18,25 +18,38 @@
 
 package org.apache.zookeeper.server;
 
-import static org.apache.zookeeper.common.X509Exception.SSLContextException;
 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.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;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.jboss.netty.buffer.ChannelBuffers;
 import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.ChannelFutureListener;
 import org.jboss.netty.channel.ChannelHandler.Sharable;
 import org.jboss.netty.channel.ChannelHandlerContext;
 import org.jboss.netty.channel.ChannelPipeline;
@@ -90,11 +103,19 @@ public class NettyServerCnxnFactory exte
             if (LOG.isTraceEnabled()) {
                 LOG.trace("Channel connected " + e);
             }
-            allChannels.add(ctx.getChannel());
+
             NettyServerCnxn cnxn = new NettyServerCnxn(ctx.getChannel(),
                     zkServer, NettyServerCnxnFactory.this);
             ctx.setAttachment(cnxn);
-            addCnxn(cnxn);
+
+            if (secure) {
+                SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class);
+                ChannelFuture handshakeFuture = sslHandler.handshake();
+                handshakeFuture.addListener(new CertificateVerifier(sslHandler, cnxn));
+            } else {
+                allChannels.add(ctx.getChannel());
+                addCnxn(cnxn);
+            }
         }
 
         @Override
@@ -247,6 +268,58 @@ public class NettyServerCnxnFactory exte
             }
         }
 
+        private final class CertificateVerifier
+                implements ChannelFutureListener {
+            private final SslHandler sslHandler;
+            private final NettyServerCnxn cnxn;
+
+            CertificateVerifier(SslHandler sslHandler, NettyServerCnxn cnxn) {
+                this.sslHandler = sslHandler;
+                this.cnxn = cnxn;
+            }
+
+            /**
+             * Only allow the connection to stay open if certificate passes auth
+             */
+            public void operationComplete(ChannelFuture future)
+                    throws SSLPeerUnverifiedException {
+                if (future.isSuccess()) {
+                    LOG.debug("Successful handshake with session 0x{}",
+                            Long.toHexString(cnxn.sessionId));
+                    SSLEngine eng = sslHandler.getEngine();
+                    SSLSession session = eng.getSession();
+                    cnxn.setClientCertificateChain(session.getPeerCertificates());
+
+                    String authProviderProp
+                            = System.getProperty(X509Util.SSL_AUTHPROVIDER, "x509");
+
+                    X509AuthenticationProvider authProvider =
+                            (X509AuthenticationProvider)
+                                    ProviderRegistry.getProvider(authProviderProp);
+
+                    if (authProvider == null) {
+                        LOG.error("Auth provider not found: {}", authProviderProp);
+                        cnxn.close();
+                        return;
+                    }
+
+                    if (KeeperException.Code.OK !=
+                            authProvider.handleAuthentication(cnxn, null)) {
+                        LOG.error("Authentication failed for session 0x{}",
+                                Long.toHexString(cnxn.sessionId));
+                        cnxn.close();
+                        return;
+                    }
+
+                    allChannels.add(future.getChannel());
+                    addCnxn(cnxn);
+                } else {
+                    LOG.error("Unsuccessful handshake with session 0x{}",
+                            Long.toHexString(cnxn.sessionId));
+                    cnxn.close();
+                }
+            }
+        }
     }
     
     CnxnChannelHandler channelHandler = new CnxnChannelHandler();
@@ -276,8 +349,32 @@ public class NettyServerCnxnFactory exte
         });
     }
 
-    private synchronized void initSSL(ChannelPipeline p) throws SSLContextException {
-        SSLContext sslContext = X509Util.createSSLContext();
+    private synchronized void initSSL(ChannelPipeline p)
+            throws X509Exception, KeyManagementException, NoSuchAlgorithmException {
+        String authProviderProp = System.getProperty(X509Util.SSL_AUTHPROVIDER);
+        SSLContext sslContext;
+        if (authProviderProp == null) {
+            sslContext = X509Util.createSSLContext();
+        } else {
+            sslContext = SSLContext.getInstance("TLSv1");
+            X509AuthenticationProvider authProvider =
+                    (X509AuthenticationProvider)ProviderRegistry.getProvider(
+                            System.getProperty(X509Util.SSL_AUTHPROVIDER,
+                                    "x509"));
+
+            if (authProvider == null)
+            {
+                LOG.error("Auth provider not found: {}", authProviderProp);
+                throw new SSLContextException(
+                        "Could not create SSLContext with specified auth provider: " +
+                        authProviderProp);
+            }
+
+            sslContext.init(new X509KeyManager[] { authProvider.getKeyManager() },
+                            new X509TrustManager[] { authProvider.getTrustManager() },
+                            null);
+        }
+
         SSLEngine sslEngine = sslContext.createSSLEngine();
         sslEngine.setUseClientMode(false);
         sslEngine.setNeedClientAuth(true);

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/ServerCnxn.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/ServerCnxn.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/ServerCnxn.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/ServerCnxn.java Sat Mar 28 19:08:52 2015
@@ -23,6 +23,7 @@ import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
+import java.security.cert.Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
@@ -69,7 +70,7 @@ public abstract class ServerCnxn impleme
 
     public abstract void process(WatchedEvent event);
 
-    abstract long getSessionId();
+    public abstract long getSessionId();
 
     abstract void setSessionId(long sessionId);
 
@@ -407,6 +408,9 @@ public abstract class ServerCnxn impleme
 
     public abstract InetSocketAddress getRemoteSocketAddress();
     public abstract int getInterestOps();
+    public abstract boolean isSecure();
+    public abstract Certificate[] getClientCertificateChain();
+    public abstract void setClientCertificateChain(Certificate[] chain);
     
     /**
      * Print information about the connection.

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java Sat Mar 28 19:08:52 2015
@@ -79,8 +79,7 @@ public abstract class ServerCnxnFactory
             throws IOException;
 
     public abstract void reconfigure(InetSocketAddress addr);
-    
-    
+
     protected SaslServerCallbackHandler saslServerCallbackHandler;
     public Login login;
 
@@ -90,6 +89,10 @@ public abstract class ServerCnxnFactory
     /** Maximum number of connections allowed from particular host (ip) */
     public abstract void setMaxClientCnxnsPerHost(int max);
 
+    public boolean isSecure() {
+        return secure;
+    }
+
     public void startup(ZooKeeperServer zkServer) throws IOException, InterruptedException {
         startup(zkServer, true);
     }

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/auth/ProviderRegistry.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/auth/ProviderRegistry.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/auth/ProviderRegistry.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/auth/ProviderRegistry.java Sat Mar 28 19:08:52 2015
@@ -39,8 +39,10 @@ public class ProviderRegistry {
                 return;
             IPAuthenticationProvider ipp = new IPAuthenticationProvider();
             DigestAuthenticationProvider digp = new DigestAuthenticationProvider();
+            X509AuthenticationProvider x509p = new X509AuthenticationProvider();
             authenticationProviders.put(ipp.getScheme(), ipp);
             authenticationProviders.put(digp.getScheme(), digp);
+            authenticationProviders.put(x509p.getScheme(), x509p);
             Enumeration<Object> en = System.getProperties().keys();
             while (en.hasMoreElements()) {
                 String k = (String) en.nextElement();

Added: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java?rev=1669825&view=auto
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java (added)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java Sat Mar 28 19:08:52 2015
@@ -0,0 +1,226 @@
+/**
+ * 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.auth;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.common.X509Exception.KeyManagerException;
+import org.apache.zookeeper.common.X509Exception.TrustManagerException;
+import org.apache.zookeeper.common.X509Util;
+import org.apache.zookeeper.data.Id;
+import org.apache.zookeeper.server.ServerCnxn;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An AuthenticationProvider backed by an X509TrustManager and an X509KeyManager
+ * to perform remote host certificate authentication. The default algorithm is
+ * SunX509 and a JKS KeyStore. To specify the locations of the key store and
+ * trust store, set the following system properties:
+ * <br/><code>zookeeper.ssl.keyStore.location</code>
+ * <br/><code>zookeeper.ssl.trustStore.location</code>
+ * <br/>To specify store passwords, set the following system properties:
+ * <br/><code>zookeeper.ssl.keyStore.password</code>
+ * <br/><code>zookeeper.ssl.trustStore.password</code>
+ * <br/>Alternatively, this can be plugged with any X509TrustManager and
+ * X509KeyManager implementation.
+ */
+public class X509AuthenticationProvider implements AuthenticationProvider {
+    static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER
+            = "zookeeper.X509AuthenticationProvider.superUser";
+    private static final Logger LOG
+            = LoggerFactory.getLogger(X509AuthenticationProvider.class);
+    private final X509TrustManager trustManager;
+    private final X509KeyManager keyManager;
+
+    /**
+     * Initialize the X509AuthenticationProvider with a JKS KeyStore and JKS
+     * TrustStore according to the following system properties:
+     * <br/><code>zookeeper.ssl.keyStore.location</code>
+     * <br/><code>zookeeper.ssl.trustStore.location</code>
+     * <br/><code>zookeeper.ssl.keyStore.password</code>
+     * <br/><code>zookeeper.ssl.trustStore.password</code>
+     */
+    public X509AuthenticationProvider() {
+        String keyStoreLocationProp = System.getProperty(
+                X509Util.SSL_KEYSTORE_LOCATION);
+        String keyStorePasswordProp = System.getProperty(
+                X509Util.SSL_KEYSTORE_PASSWD);
+
+        X509KeyManager km = null;
+        X509TrustManager tm = null;
+        try {
+            km = X509Util.createKeyManager(
+                    keyStoreLocationProp, keyStorePasswordProp);
+        } catch (KeyManagerException e) {
+            LOG.error("Failed to create key manager", e);
+        }
+
+        String trustStoreLocationProp = System.getProperty(
+                X509Util.SSL_TRUSTSTORE_LOCATION);
+        String trustStorePasswordProp = System.getProperty(
+                X509Util.SSL_TRUSTSTORE_PASSWD);
+
+        try {
+            tm = X509Util.createTrustManager(
+                    trustStoreLocationProp, trustStorePasswordProp);
+        } catch (TrustManagerException e) {
+            LOG.error("Failed to create trust manager", e);
+        }
+
+        this.keyManager = km;
+        this.trustManager = tm;
+    }
+
+    /**
+     * Initialize the X509AuthenticationProvider with the provided
+     * X509TrustManager and X509KeyManager.
+     *
+     * @param trustManager X509TrustManager implementation to use for remote
+     *                     host authentication.
+     * @param keyManager   X509KeyManager implementation to use for certificate
+     *                     management.
+     */
+    public X509AuthenticationProvider(X509TrustManager trustManager,
+                                      X509KeyManager keyManager) {
+        this.trustManager = trustManager;
+        this.keyManager = keyManager;
+    }
+
+    @Override
+    public String getScheme() {
+        return "x509";
+    }
+
+    @Override
+    public KeeperException.Code handleAuthentication(ServerCnxn cnxn,
+                                                     byte[] authData) {
+        X509Certificate[] certChain
+                = (X509Certificate[]) cnxn.getClientCertificateChain();
+
+        if (certChain == null || certChain.length == 0) {
+            return KeeperException.Code.AUTHFAILED;
+        }
+
+        if (trustManager == null) {
+            LOG.error("No trust manager available to authenticate session 0x{}",
+                    Long.toHexString(cnxn.getSessionId()));
+            return KeeperException.Code.AUTHFAILED;
+        }
+
+        X509Certificate clientCert = certChain[0];
+
+        try {
+            // Authenticate client certificate
+            trustManager.checkClientTrusted(certChain,
+                    clientCert.getPublicKey().getAlgorithm());
+        } catch (CertificateException ce) {
+            LOG.error("Failed to trust certificate for session 0x" +
+                    Long.toHexString(cnxn.getSessionId()), ce);
+            return KeeperException.Code.AUTHFAILED;
+        }
+
+        String clientId = getClientId(clientCert);
+
+        if (clientId.equals(System.getProperty(
+                ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER))) {
+            cnxn.addAuthInfo(new Id("super", clientId));
+            LOG.info("Authenticated Id '{}' as super user", clientId);
+        }
+
+        Id authInfo = new Id(getScheme(), clientId);
+        cnxn.addAuthInfo(authInfo);
+
+        LOG.info("Authenticated Id '{}' for Scheme '{}'",
+                authInfo.getId(), authInfo.getScheme());
+        return KeeperException.Code.OK;
+    }
+
+    /**
+     * Determine the string to be used as the remote host session Id for
+     * authorization purposes. Associate this client identifier with a
+     * ServerCnxn that has been authenticated over SSL, and any ACLs that refer
+     * to the authenticated client.
+     *
+     * @param clientCert Authenticated X509Certificate associated with the
+     *                   remote host.
+     * @return Identifier string to be associated with the client.
+     */
+    protected String getClientId(X509Certificate clientCert) {
+        return clientCert.getSubjectX500Principal().getName();
+    }
+
+    @Override
+    public boolean matches(String id, String aclExpr) {
+        if (System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER) != null) {
+            return (id.equals(System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER))
+                    || id.equals(aclExpr));
+        }
+
+        return (id.equals(aclExpr));
+    }
+
+    @Override
+    public boolean isAuthenticated() {
+        return true;
+    }
+
+    @Override
+    public boolean isValid(String id) {
+        try {
+            new X500Principal(id);
+            return true;
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Get the X509TrustManager implementation used for remote host
+     * authentication.
+     *
+     * @return The X509TrustManager.
+     * @throws TrustManagerException When there is no trust manager available.
+     */
+    public X509TrustManager getTrustManager() throws TrustManagerException {
+        if (trustManager == null) {
+            throw new TrustManagerException("No trust manager available");
+        }
+        return trustManager;
+    }
+
+    /**
+     * Get the X509KeyManager implementation used for certificate management.
+     *
+     * @return The X509KeyManager.
+     * @throws KeyManagerException When there is no key manager available.
+     */
+    public X509KeyManager getKeyManager() throws KeyManagerException {
+        if (keyManager == null) {
+            throw new KeyManagerException("No key manager available");
+        }
+        return keyManager;
+    }
+}

Added: zookeeper/trunk/src/java/test/org/apache/zookeeper/server/MockServerCnxn.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/server/MockServerCnxn.java?rev=1669825&view=auto
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/server/MockServerCnxn.java (added)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/server/MockServerCnxn.java Sat Mar 28 19:08:52 2015
@@ -0,0 +1,113 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.security.cert.Certificate;
+import org.apache.jute.Record;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.proto.ReplyHeader;
+
+public class MockServerCnxn extends ServerCnxn {
+    public Certificate[] clientChain;
+    public boolean secure;
+
+    @Override
+    int getSessionTimeout() {
+        return 0;
+    }
+
+    @Override
+    void close() {
+    }
+
+    @Override
+    public void sendResponse(ReplyHeader h, Record r, String tag)
+            throws IOException {
+    }
+
+    @Override
+    void sendCloseSession() {
+    }
+
+    @Override
+    public void process(WatchedEvent event) {
+    }
+
+    @Override
+    public long getSessionId() {
+        return 0;
+    }
+
+    @Override
+    void setSessionId(long sessionId) {
+    }
+
+    @Override
+    public boolean isSecure() {
+        return secure;
+    }
+
+    @Override
+    public Certificate[] getClientCertificateChain() {
+        return clientChain;
+    }
+
+    @Override
+    public void setClientCertificateChain(Certificate[] chain) {
+        clientChain = chain;
+    }
+
+    @Override
+    void sendBuffer(ByteBuffer closeConn) {
+    }
+
+    @Override
+    void enableRecv() {
+    }
+
+    @Override
+    void disableRecv() {
+    }
+
+    @Override
+    void setSessionTimeout(int sessionTimeout) {
+    }
+
+    @Override
+    protected ServerStats serverStats() {
+        return null;
+    }
+
+    @Override
+    public long getOutstandingRequests() {
+        return 0;
+    }
+
+    @Override
+    public InetSocketAddress getRemoteSocketAddress() {
+        return null;
+    }
+
+    @Override
+    public int getInterestOps() {
+        return 0;
+    }
+}
\ No newline at end of file

Modified: zookeeper/trunk/src/java/test/org/apache/zookeeper/test/ClientBase.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/test/ClientBase.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/test/ClientBase.java (original)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/test/ClientBase.java Sat Mar 28 19:08:52 2015
@@ -43,6 +43,7 @@ import javax.management.ObjectName;
 import junit.framework.TestCase;
 
 import org.apache.zookeeper.common.Time;
+import org.apache.zookeeper.common.X509Exception.SSLContextException;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.PortAssignment;
 import org.apache.zookeeper.TestableZooKeeper;
@@ -56,7 +57,6 @@ import org.apache.zookeeper.server.Serve
 import org.apache.zookeeper.server.ServerCnxnFactoryAccessor;
 import org.apache.zookeeper.server.ZKDatabase;
 import org.apache.zookeeper.server.ZooKeeperServer;
-import org.apache.zookeeper.server.ZooKeeperServerListener;
 import org.apache.zookeeper.server.persistence.FileTxnLog;
 import org.apache.zookeeper.server.quorum.QuorumPeer;
 import org.apache.zookeeper.server.util.OSMXBean;
@@ -230,19 +230,25 @@ public abstract class ClientBase extends
     }
 
     public static boolean waitForServerUp(String hp, long timeout) {
+        return waitForServerUp(hp, timeout, false);
+    }
+
+    public static boolean waitForServerUp(String hp, long timeout, boolean secure) {
         long start = Time.currentElapsedTime();
         while (true) {
             try {
                 // if there are multiple hostports, just take the first one
                 HostPort hpobj = parseHostPortList(hp).get(0);
-                String result = send4LetterWord(hpobj.host, hpobj.port, "stat");
+                String result = send4LetterWord(hpobj.host, hpobj.port, "stat", secure);
                 if (result.startsWith("Zookeeper version:") &&
                         !result.contains("READ-ONLY")) {
                     return true;
                 }
             } catch (IOException e) {
                 // ignore as this is expected
-                LOG.info("server " + hp + " not up " + e);
+                LOG.info("server {} not up", hp, e);
+            } catch (SSLContextException e) {
+                LOG.error("server {} not up", hp, e);
             }
 
             if (Time.currentElapsedTime() > start + timeout) {
@@ -256,14 +262,21 @@ public abstract class ClientBase extends
         }
         return false;
     }
+
     public static boolean waitForServerDown(String hp, long timeout) {
+        return waitForServerDown(hp, timeout, false);
+    }
+
+    public static boolean waitForServerDown(String hp, long timeout, boolean secure) {
         long start = Time.currentElapsedTime();
         while (true) {
             try {
                 HostPort hpobj = parseHostPortList(hp).get(0);
-                send4LetterWord(hpobj.host, hpobj.port, "stat");
+                send4LetterWord(hpobj.host, hpobj.port, "stat", secure);
             } catch (IOException e) {
                 return true;
+            } catch (SSLContextException e) {
+                return true;
             }
 
             if (Time.currentElapsedTime() > start + timeout) {
@@ -311,6 +324,7 @@ public abstract class ClientBase extends
     public static File createTmpDir() throws IOException {
         return createTmpDir(BASETEST);
     }
+
     static File createTmpDir(File parentDir) throws IOException {
         File tmpFile = File.createTempFile("test", ".junit", parentDir);
         // don't delete tmpFile - this ensures we don't attempt to create
@@ -321,6 +335,7 @@ public abstract class ClientBase extends
 
         return tmpDir;
     }
+
     private static int getPort(String hostPort) {
         String[] split = hostPort.split(":");
         String portstr = split[split.length-1];
@@ -342,7 +357,7 @@ public abstract class ClientBase extends
         ZooKeeperServer zks = new ZooKeeperServer(dataDir, dataDir, 3000);
         factory.startup(zks);
         Assert.assertTrue("waiting for server up", ClientBase.waitForServerUp(
-                "127.0.0.1:" + port, CONNECTION_TIMEOUT));
+                "127.0.0.1:" + port, CONNECTION_TIMEOUT, factory.isSecure()));
     }
 
     /**
@@ -393,7 +408,8 @@ public abstract class ClientBase extends
 
             Assert.assertTrue("waiting for server down",
                        ClientBase.waitForServerDown("127.0.0.1:" + PORT,
-                                                    CONNECTION_TIMEOUT));
+                                                    CONNECTION_TIMEOUT,
+                                                    factory.isSecure()));
         }
     }
 
@@ -664,14 +680,4 @@ public abstract class ClientBase extends
         }
         return sb.toString();
     }
-
-    public ZooKeeperServerListener testZKSListener() {
-        return new ZooKeeperServerListener() {
-
-            @Override
-            public void notifyStopping(String errMsg, int exitCode) {
-
-            }
-        };
-    }
 }

Modified: zookeeper/trunk/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java (original)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java Sat Mar 28 19:08:52 2015
@@ -23,7 +23,10 @@ import java.io.IOException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.zookeeper.TestableZooKeeper;
+import org.apache.zookeeper.common.X509Exception.SSLContextException;
+
 import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord;
+
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -97,7 +100,7 @@ public class FourLetterWordsQuorumTest e
     }
 
     private void verify(String hp, String cmd, String expected)
-        throws IOException
+        throws IOException, SSLContextException
     {
         for(HostPort hpobj: parseHostPortList(hp)) {
             String resp = send4LetterWord(hpobj.host, hpobj.port, cmd);

Modified: zookeeper/trunk/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java?rev=1669825&r1=1669824&r2=1669825&view=diff
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java (original)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java Sat Mar 28 19:08:52 2015
@@ -25,7 +25,10 @@ import java.util.regex.Pattern;
 
 import org.apache.zookeeper.TestableZooKeeper;
 import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.common.X509Exception.SSLContextException;
+
 import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord;
+
 import org.junit.Assert;
 import org.junit.Test;
 import org.slf4j.Logger;
@@ -99,12 +102,12 @@ public class FourLetterWordsTest extends
         verify("srvr", "Connections");
     }
 
-    private String sendRequest(String cmd) throws IOException {
+    private String sendRequest(String cmd) throws IOException, SSLContextException {
       HostPort hpobj = ClientBase.parseHostPortList(hostPort).get(0);
       return send4LetterWord(hpobj.host, hpobj.port, cmd);
     }
 
-    private void verify(String cmd, String expected) throws IOException {
+    private void verify(String cmd, String expected) throws IOException, SSLContextException {
         String resp = sendRequest(cmd);
         LOG.info("cmd " + cmd + " expected " + expected + " got " + resp);
         Assert.assertTrue(resp.contains(expected));

Added: zookeeper/trunk/src/java/test/org/apache/zookeeper/test/SSLAuthTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/test/SSLAuthTest.java?rev=1669825&view=auto
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/test/SSLAuthTest.java (added)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/test/SSLAuthTest.java Sat Mar 28 19:08:52 2015
@@ -0,0 +1,99 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.test;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.zookeeper.PortAssignment;
+import org.apache.zookeeper.TestableZooKeeper;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.common.X509Util;
+import org.apache.zookeeper.server.ServerCnxnFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SSLAuthTest extends ClientBase {
+    @Before
+    public void setUp() throws Exception {
+        String testDataPath = System.getProperty("test.data.dir", "build/test/data");
+        System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, "org.apache.zookeeper.server.NettyServerCnxnFactory");
+        System.setProperty(ZooKeeper.ZOOKEEPER_CLIENT_CNXN_SOCKET, "org.apache.zookeeper.ClientCnxnSocketNetty");
+        System.setProperty(ZooKeeper.SECURE_CLIENT, "true");
+        System.setProperty(X509Util.SSL_AUTHPROVIDER, "x509");
+        System.setProperty(X509Util.SSL_KEYSTORE_LOCATION, testDataPath + "/ssl/testKeyStore.jks");
+        System.setProperty(X509Util.SSL_KEYSTORE_PASSWD, "testpass");
+        System.setProperty(X509Util.SSL_TRUSTSTORE_LOCATION, testDataPath + "/ssl/testTrustStore.jks");
+        System.setProperty(X509Util.SSL_TRUSTSTORE_PASSWD, "testpass");
+        System.setProperty("javax.net.debug", "ssl");
+
+        String host = "localhost";
+        int port = PortAssignment.unique();
+        hostPort = host + ":" + port;
+
+        serverFactory = ServerCnxnFactory.createFactory();
+        serverFactory.configure(new InetSocketAddress(host, port), maxCnxns, true);
+
+        super.setUp();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY);
+        System.clearProperty(ZooKeeper.ZOOKEEPER_CLIENT_CNXN_SOCKET);
+        System.clearProperty(ZooKeeper.SECURE_CLIENT);
+        System.clearProperty(X509Util.SSL_AUTHPROVIDER);
+        System.clearProperty(X509Util.SSL_KEYSTORE_LOCATION);
+        System.clearProperty(X509Util.SSL_KEYSTORE_PASSWD);
+        System.clearProperty(X509Util.SSL_TRUSTSTORE_LOCATION);
+        System.clearProperty(X509Util.SSL_TRUSTSTORE_PASSWD);
+        System.clearProperty("javax.net.debug");
+    }
+
+    @Test
+    public void testRejection() throws Exception {
+        String testDataPath = System.getProperty("test.data.dir", "build/test/data");
+
+        // Replace trusted keys with a valid key that is not trusted by the server
+        System.setProperty(X509Util.SSL_KEYSTORE_LOCATION, testDataPath + "/ssl/testUntrustedKeyStore.jks");
+        System.setProperty(X509Util.SSL_KEYSTORE_PASSWD, "testpass");
+
+        CountdownWatcher watcher = new CountdownWatcher();
+
+        // Handshake will take place, and then X509AuthenticationProvider should reject the untrusted cert
+        new TestableZooKeeper(hostPort, CONNECTION_TIMEOUT, watcher);
+        Assert.assertFalse("Untrusted certificate should not result in successful connection",
+                watcher.clientConnected.await(1000, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testMisconfiguration() throws Exception {
+        System.clearProperty(X509Util.SSL_AUTHPROVIDER);
+        System.clearProperty(X509Util.SSL_KEYSTORE_LOCATION);
+        System.clearProperty(X509Util.SSL_KEYSTORE_PASSWD);
+        System.clearProperty(X509Util.SSL_TRUSTSTORE_LOCATION);
+        System.clearProperty(X509Util.SSL_TRUSTSTORE_PASSWD);
+
+        CountdownWatcher watcher = new CountdownWatcher();
+        new TestableZooKeeper(hostPort, CONNECTION_TIMEOUT, watcher);
+        Assert.assertFalse("Missing SSL configuration should not result in successful connection",
+                watcher.clientConnected.await(1000, TimeUnit.MILLISECONDS));
+    }
+}
\ No newline at end of file

Added: zookeeper/trunk/src/java/test/org/apache/zookeeper/test/X509AuthTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/test/X509AuthTest.java?rev=1669825&view=auto
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/test/X509AuthTest.java (added)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/test/X509AuthTest.java Sat Mar 28 19:08:52 2015
@@ -0,0 +1,290 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.test;
+
+import java.math.BigInteger;
+import java.net.Socket;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Set;
+
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+
+import junit.framework.Assert;
+
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZKTestCase;
+import org.apache.zookeeper.server.MockServerCnxn;
+import org.apache.zookeeper.server.auth.X509AuthenticationProvider;
+import org.junit.Before;
+import org.junit.Test;
+
+public class X509AuthTest extends ZKTestCase {
+    private static TestCertificate clientCert;
+    private static TestCertificate superCert;
+    private static TestCertificate unknownCert;
+
+    @Before
+    public void setUp() {
+        System.setProperty("zookeeper.X509AuthenticationProvider.superUser",
+                "CN=SUPER");
+        System.setProperty("zookeeper.ssl.keyManager",
+                "org.apache.zookeeper.test.X509AuthTest.TestKeyManager");
+        System.setProperty("zookeeper.ssl.trustManager",
+                "org.apache.zookeeper.test.X509AuthTest.TestTrustManager");
+
+        clientCert = new TestCertificate("CLIENT");
+        superCert = new TestCertificate("SUPER");
+        unknownCert = new TestCertificate("UNKNOWN");
+    }
+
+    @Test
+    public void testTrustedAuth() {
+        X509AuthenticationProvider provider = createProvider(clientCert);
+        MockServerCnxn cnxn = new MockServerCnxn();
+        cnxn.clientChain = new X509Certificate[] { clientCert };
+        Assert.assertEquals(KeeperException.Code.OK, provider.handleAuthentication(cnxn, null));
+    }
+
+    @Test
+    public void testSuperAuth() {
+        X509AuthenticationProvider provider = createProvider(superCert);
+        MockServerCnxn cnxn = new MockServerCnxn();
+        cnxn.clientChain = new X509Certificate[] { superCert };
+        Assert.assertEquals(KeeperException.Code.OK, provider.handleAuthentication(cnxn, null));
+        Assert.assertEquals("super", cnxn.getAuthInfo().get(0).getScheme());
+    }
+
+    @Test
+    public void testUntrustedAuth() {
+        X509AuthenticationProvider provider = createProvider(clientCert);
+        MockServerCnxn cnxn = new MockServerCnxn();
+        cnxn.clientChain = new X509Certificate[] { unknownCert };
+        Assert.assertEquals(KeeperException.Code.AUTHFAILED, provider.handleAuthentication(cnxn, null));
+    }
+
+    private static class TestPublicKey implements PublicKey {
+        private static final long serialVersionUID = 1L;
+        @Override
+        public String getAlgorithm() {
+            return null;
+        }
+        @Override
+        public String getFormat() {
+            return null;
+        }
+        @Override
+        public byte[] getEncoded() {
+            return null;
+        }
+    }
+    private static class TestCertificate extends X509Certificate {
+        private byte[] encoded;
+        private X500Principal principal;
+        private PublicKey publicKey;
+        public TestCertificate(String name) {
+            encoded = name.getBytes();
+            principal = new X500Principal("CN=" + name);
+            publicKey = new TestPublicKey();
+        }
+        @Override
+        public boolean hasUnsupportedCriticalExtension() {
+            return false;
+        }
+        @Override
+        public Set<String> getCriticalExtensionOIDs() {
+            return null;
+        }
+        @Override
+        public Set<String> getNonCriticalExtensionOIDs() {
+            return null;
+        }
+        @Override
+        public byte[] getExtensionValue(String oid) {
+            return null;
+        }
+        @Override
+        public void checkValidity() throws CertificateExpiredException,
+                CertificateNotYetValidException {
+        }
+        @Override
+        public void checkValidity(Date date)
+                throws CertificateExpiredException,
+                CertificateNotYetValidException {
+        }
+        @Override
+        public int getVersion() {
+            return 0;
+        }
+        @Override
+        public BigInteger getSerialNumber() {
+            return null;
+        }
+        @Override
+        public Principal getIssuerDN() {
+            return null;
+        }
+        @Override
+        public Principal getSubjectDN() {
+            return null;
+        }
+        @Override
+        public Date getNotBefore() {
+            return null;
+        }
+        @Override
+        public Date getNotAfter() {
+            return null;
+        }
+        @Override
+        public byte[] getTBSCertificate() throws CertificateEncodingException {
+            return null;
+        }
+        @Override
+        public byte[] getSignature() {
+            return null;
+        }
+        @Override
+        public String getSigAlgName() {
+            return null;
+        }
+        @Override
+        public String getSigAlgOID() {
+            return null;
+        }
+        @Override
+        public byte[] getSigAlgParams() {
+            return null;
+        }
+        @Override
+        public boolean[] getIssuerUniqueID() {
+            return null;
+        }
+        @Override
+        public boolean[] getSubjectUniqueID() {
+            return null;
+        }
+        @Override
+        public boolean[] getKeyUsage() {
+            return null;
+        }
+        @Override
+        public int getBasicConstraints() {
+            return 0;
+        }
+        @Override
+        public byte[] getEncoded() throws CertificateEncodingException {
+            return encoded;
+        }
+        @Override
+        public void verify(PublicKey key) throws CertificateException,
+                NoSuchAlgorithmException, InvalidKeyException,
+                NoSuchProviderException, SignatureException {
+        }
+        @Override
+        public void verify(PublicKey key, String sigProvider)
+                throws CertificateException, NoSuchAlgorithmException,
+                InvalidKeyException, NoSuchProviderException,
+                SignatureException {
+        }
+        @Override
+        public String toString() {
+            return null;
+        }
+        @Override
+        public PublicKey getPublicKey() {
+            return publicKey;
+        }
+        @Override
+        public X500Principal getSubjectX500Principal() {
+            return principal;
+        }
+    }
+    public static class TestKeyManager implements X509KeyManager {
+        @Override
+        public String chooseClientAlias(String[] keyType, Principal[] issuers,
+                Socket socket) {
+            return null;
+        }
+        @Override
+        public String chooseServerAlias(String keyType, Principal[] issuers,
+                Socket socket) {
+            return null;
+        }
+        @Override
+        public X509Certificate[] getCertificateChain(String alias) {
+            return null;
+        }
+        @Override
+        public String[] getClientAliases(String keyType, Principal[] issuers) {
+            return null;
+        }
+        @Override
+        public PrivateKey getPrivateKey(String alias) {
+            return null;
+        }
+        @Override
+        public String[] getServerAliases(String keyType, Principal[] issuers) {
+            return null;
+        }
+    }
+    public static class TestTrustManager implements X509TrustManager {
+        X509Certificate cert;
+        public TestTrustManager(X509Certificate testCert) {
+            cert = testCert;
+        }
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType)
+                throws CertificateException {
+            if (!Arrays.equals(cert.getEncoded(), chain[0].getEncoded())) {
+                throw new CertificateException("Client cert not trusted");
+            }
+        }
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType)
+                throws CertificateException {
+            if (!Arrays.equals(cert.getEncoded(), chain[0].getEncoded())) {
+                throw new CertificateException("Server cert not trusted");
+            }
+        }
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return null;
+        }
+    }
+
+    protected X509AuthenticationProvider createProvider(X509Certificate trustedCert) {
+        return new X509AuthenticationProvider(
+                new TestTrustManager(trustedCert),
+                new TestKeyManager());
+    }
+}



Mime
View raw message