Author: markt
Date: Tue Mar 26 21:17:18 2013
New Revision: 1461317

URL: http://svn.apache.org/r1461317
Log:
First cut of an SSL client implementation for WebSocket and a simple test case 
to demonstrate that it works.

Modified:
    tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
    
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java
    tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
    tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
    tomcat/trunk/test/org/apache/tomcat/util/net/TesterSupport.java
    tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java

Modified: tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java 
(original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java Tue 
Mar 26 21:17:18 2013
@@ -21,6 +21,8 @@ import java.nio.channels.CompletionHandl
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 
+import javax.net.ssl.SSLException;
+
 /**
  * This is a wrapper for a {@link java.nio.channels.AsynchronousSocketChannel}
  * that limits the methods available thereby simplifying the process of
@@ -40,4 +42,6 @@ public interface AsyncChannelWrapper {
             CompletionHandler<Long,? super A> handler);
 
     void close();
+
+    Future<Void> handshake() throws SSLException;
 }

Modified: 
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
--- 
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java 
(original)
+++ 
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java 
Tue Mar 26 21:17:18 2013
@@ -20,8 +20,10 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.CompletionHandler;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Generally, just passes calls straight to the wrapped
@@ -30,6 +32,8 @@ import java.util.concurrent.TimeUnit;
  */
 public class AsyncChannelWrapperNonSecure implements AsyncChannelWrapper {
 
+    private static final Future<Void> NOOP_FUTURE = new NoOpFuture();
+
     private final AsynchronousSocketChannel socketChannel;
 
     public AsyncChannelWrapperNonSecure(
@@ -69,4 +73,40 @@ public class AsyncChannelWrapperNonSecur
             // Ignore
         }
     }
+
+    @Override
+    public Future<Void> handshake() {
+        return NOOP_FUTURE;
+    }
+
+
+    private static final class NoOpFuture implements Future<Void> {
+
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            return false;
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return false;
+        }
+
+        @Override
+        public boolean isDone() {
+            return true;
+        }
+
+        @Override
+        public Void get() throws InterruptedException, ExecutionException {
+            return null;
+        }
+
+        @Override
+        public Void get(long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException,
+                TimeoutException {
+            return null;
+        }
+    }
 }

Modified: 
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
--- 
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java 
(original)
+++ 
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java 
Tue Mar 26 21:17:18 2013
@@ -16,53 +16,505 @@
  */
 package org.apache.tomcat.websocket;
 
+import java.io.EOFException;
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.CompletionHandler;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
 
 /**
- * Wraps the {@link AsynchronousSocketChannel} with SSL/TLS.
+ * Wraps the {@link AsynchronousSocketChannel} with SSL/TLS. This needs a lot
+ * more testing before it can be considered robust.
  */
 public class AsyncChannelWrapperSecure implements AsyncChannelWrapper {
 
+    private static final ByteBuffer DUMMY = ByteBuffer.allocate(8192);
     private final AsynchronousSocketChannel socketChannel;
+    private final SSLEngine sslEngine;
+    private final ByteBuffer socketReadBuffer;
+    private final ByteBuffer socketWriteBuffer;
+    // One thread for read, one for write
+    private final ExecutorService executor = Executors.newFixedThreadPool(2);
+    private AtomicBoolean writing = new AtomicBoolean(false);
+    private AtomicBoolean reading = new AtomicBoolean(false);
 
-    public AsyncChannelWrapperSecure(AsynchronousSocketChannel socketChannel) {
+    public AsyncChannelWrapperSecure(AsynchronousSocketChannel socketChannel,
+            SSLEngine sslEngine) {
         this.socketChannel = socketChannel;
+        this.sslEngine = sslEngine;
+
+        int socketBufferSize = sslEngine.getSession().getPacketBufferSize();
+        socketReadBuffer = ByteBuffer.allocateDirect(socketBufferSize);
+        socketWriteBuffer = ByteBuffer.allocateDirect(socketBufferSize);
     }
 
     @Override
     public Future<Integer> read(ByteBuffer dst) {
-        // TODO
-        throw new UnsupportedOperationException();
+        WrapperFuture<Integer,Void> future = new WrapperFuture<>();
+
+        if (!reading.compareAndSet(false, true)) {
+            // TODO
+            throw new IllegalStateException();
+        }
+
+        ReadTask readTask = new ReadTask(dst, future);
+
+        executor.execute(readTask);
+
+        return future;
     }
 
     @Override
     public <A> void read(ByteBuffer dst, A attachment,
             CompletionHandler<Integer,? super A> handler) {
-        // TODO
-        throw new UnsupportedOperationException();
+
+        WrapperFuture<Integer,? super A> future =
+                new WrapperFuture<>(handler, attachment);
+
+        if (!reading.compareAndSet(false, true)) {
+            // TODO
+            throw new IllegalStateException();
+        }
+
+        ReadTask readTask = new ReadTask(dst, future);
+
+        executor.execute(readTask);
     }
 
     @Override
     public Future<Integer> write(ByteBuffer src) {
-        // TODO
-        throw new UnsupportedOperationException();
+
+        WrapperFuture<Long,Void> inner = new WrapperFuture<>();
+
+        if (!writing.compareAndSet(false, true)) {
+            // TODO
+            throw new IllegalStateException();
+        }
+
+        WriteTask writeTask =
+                new WriteTask(new ByteBuffer[] {src}, 0, 1, inner);
+
+        executor.execute(writeTask);
+
+        Future<Integer> future = new LongToIntegerFuture(inner);
+        return future;
     }
 
     @Override
     public <A> void write(ByteBuffer[] srcs, int offset, int length,
             long timeout, TimeUnit unit, A attachment,
             CompletionHandler<Long,? super A> handler) {
-        // TODO
-        throw new UnsupportedOperationException();
+
+        WrapperFuture<Long,? super A> future =
+                new WrapperFuture<>(handler, attachment);
+
+        if (!writing.compareAndSet(false, true)) {
+            // TODO
+            throw new IllegalStateException();
+        }
+
+        WriteTask writeTask = new WriteTask(srcs, offset, length, future);
+
+        executor.execute(writeTask);
     }
 
     @Override
     public void close() {
-        // TODO
-        throw new UnsupportedOperationException();
+        try {
+            socketChannel.close();
+        } catch (IOException e) {
+            // TODO
+        }
+    }
+
+    @Override
+    public Future<Void> handshake() throws SSLException {
+
+        WrapperFuture<Void,Void> wFuture = new WrapperFuture<>();
+
+        Thread t = new WebSocketSslHandshakeThread(wFuture);
+        t.start();
+
+        return wFuture;
+    }
+
+
+    private class WriteTask implements Runnable {
+
+        private final ByteBuffer[] srcs;
+        private final int offset;
+        private final int length;
+        private final WrapperFuture<Long,?> future;
+
+        public WriteTask(ByteBuffer[] srcs, int offset, int length,
+                WrapperFuture<Long,?> future) {
+            this.srcs = srcs;
+            this.future = future;
+            this.offset = offset;
+            this.length = length;
+        }
+
+        @Override
+        public void run() {
+            long written = 0;
+
+            try {
+                for (int i = offset; i < offset + length; i++) {
+                    ByteBuffer src = srcs[i];
+                    while (src.hasRemaining()) {
+                        socketWriteBuffer.clear();
+
+                        // Encrypt the data
+                        SSLEngineResult r = sslEngine.wrap(src, 
socketWriteBuffer);
+                        written += r.bytesConsumed();
+                        Status s = r.getStatus();
+
+                        if (s == Status.OK || s == Status.BUFFER_OVERFLOW) {
+                            // Need to write out the bytes and may need to 
read from
+                            // the source again to empty it
+                        } else {
+                            // Status.BUFFER_UNDERFLOW - only happens on unwrap
+                            // Status.CLOSED - unexpected
+                            // TODO
+                            throw new IllegalStateException();
+                        }
+
+                        // Check for tasks
+                        if (r.getHandshakeStatus() == 
HandshakeStatus.NEED_TASK) {
+                            Runnable runnable = sslEngine.getDelegatedTask();
+                            while (runnable != null) {
+                                runnable.run();
+                                runnable = sslEngine.getDelegatedTask();
+                            }
+                        }
+
+                        socketWriteBuffer.flip();
+
+                        // Do the write
+                        Future<Integer> f = 
socketChannel.write(socketWriteBuffer);
+                        Integer socketWrite = f.get();
+                        if (socketWrite.intValue() != r.bytesProduced()) {
+                            throw new IOException();
+                        }
+                    }
+                }
+
+
+                if (writing.compareAndSet(true, false)) {
+                    future.complete(Long.valueOf(written));
+                } else {
+                    // TODO
+                    future.fail(new IllegalStateException());
+                }
+            } catch (Exception e) {
+                future.fail(e);
+            }
+        }
+    }
+
+
+    private class ReadTask implements Runnable {
+
+        private final ByteBuffer dest;
+        private final WrapperFuture<Integer,?> future;
+
+        public ReadTask(ByteBuffer dest, WrapperFuture<Integer,?> future) {
+            this.dest = dest;
+            this.future = future;
+        }
+
+        @Override
+        public void run() {
+            int read = 0;
+
+            boolean forceRead = false;
+
+            try {
+                while (read == 0) {
+                    socketReadBuffer.compact();
+
+                    if (forceRead) {
+                        Future<Integer> f =
+                                socketChannel.read(socketReadBuffer);
+                        Integer socketRead = f.get();
+                        if (socketRead.intValue() == -1) {
+                            // TODO
+                            throw new EOFException();
+                        }
+                    }
+
+                    socketReadBuffer.flip();
+
+                    if (socketReadBuffer.hasRemaining()) {
+                        // Decrypt the data in the buffer
+                        SSLEngineResult r =
+                                sslEngine.unwrap(socketReadBuffer, dest);
+                        read += r.bytesProduced();
+                        Status s = r.getStatus();
+
+                        if (s == Status.OK || s == Status.BUFFER_OVERFLOW) {
+                            // Bytes available for reading and there may be
+                            // sufficientNeed data in the socketReadBuffer to
+                            // support further reads without reading from the
+                            // socket
+                        } else if (s == Status.BUFFER_UNDERFLOW) {
+                            // There is partial data in the socketReadBuffer
+                            if (read == 0) {
+                                // Need more data before the partial data can 
be
+                                // processed and some output generated
+                                forceRead = true;
+                            }
+                            // else return the data we have and deal with the
+                            // partial data on the next read
+                        } else {
+                            // Status.CLOSED - unexpected
+                            // TODO
+                            throw new IllegalStateException();
+                        }
+
+                        // Check for tasks
+                        if (r.getHandshakeStatus() == 
HandshakeStatus.NEED_TASK) {
+                            Runnable runnable = sslEngine.getDelegatedTask();
+                            while (runnable != null) {
+                                runnable.run();
+                                runnable = sslEngine.getDelegatedTask();
+                            }
+                        }
+                    } else {
+                        forceRead = true;
+                    }
+                }
+
+
+                if (reading.compareAndSet(true, false)) {
+                    future.complete(Integer.valueOf(read));
+                } else {
+                    // TODO
+                    future.fail(new IllegalStateException());
+                }
+            } catch (Exception e) {
+                future.fail(e);
+            }
+        }
+    }
+
+
+    private class WebSocketSslHandshakeThread extends Thread {
+
+        private final WrapperFuture<Void,Void> hFuture;
+
+        public WebSocketSslHandshakeThread(WrapperFuture<Void,Void> hFuture) {
+            this.hFuture = hFuture;
+        }
+
+        @Override
+        public void run() {
+            try {
+                sslEngine.beginHandshake();
+                // So the first compact does the right thing
+                socketReadBuffer.position(socketReadBuffer.limit());
+
+                HandshakeStatus handshakeStatus =
+                        sslEngine.getHandshakeStatus();
+                boolean handshaking = true;
+
+                while(handshaking) {
+                    switch (handshakeStatus) {
+                        case NEED_WRAP: {
+                            socketWriteBuffer.clear();
+                            SSLEngineResult r =
+                                    sslEngine.wrap(DUMMY, socketWriteBuffer);
+                            handshakeStatus = checkResult(r, true);
+                            socketWriteBuffer.flip();
+                            Future<Integer> fWrite =
+                                    socketChannel.write(socketWriteBuffer);
+                            fWrite.get();
+                            break;
+                        }
+                        case NEED_UNWRAP: {
+                            socketReadBuffer.compact();
+                            if (socketReadBuffer.position() == 0) {
+                                Future<Integer> fRead =
+                                        socketChannel.read(socketReadBuffer);
+                                fRead.get();
+                            }
+                            socketReadBuffer.flip();
+                            SSLEngineResult r =
+                                    sslEngine.unwrap(socketReadBuffer, DUMMY);
+                            handshakeStatus = checkResult(r, false);
+                            break;
+                        }
+                        case NEED_TASK: {
+                            Runnable r = null;
+                            while ((r = sslEngine.getDelegatedTask()) != null) 
{
+                                r.run();
+                            }
+                            handshakeStatus = sslEngine.getHandshakeStatus();
+                            break;
+                        }
+                        case FINISHED: {
+                            handshaking = false;
+                            break;
+                        }
+                        default: {
+                            throw new SSLException("TODO");
+                        }
+                    }
+                }
+            } catch (SSLException | InterruptedException |
+                    ExecutionException e) {
+                hFuture.fail(e);
+            }
+
+            hFuture.complete(null);
+        }
+
+        private HandshakeStatus checkResult(SSLEngineResult result,
+                boolean wrap) throws SSLException {
+
+            if (result.getStatus() != Status.OK) {
+                throw new SSLException("TODO");
+            }
+            if (wrap && result.bytesConsumed() != 0) {
+                throw new SSLException("TODO");
+            }
+            if (!wrap && result.bytesProduced() != 0) {
+                throw new SSLException("TODO");
+            }
+            return result.getHandshakeStatus();
+        }
+    }
+
+
+    private static class WrapperFuture<T,A> implements Future<T> {
+
+        private final CompletionHandler<T,A> handler;
+        private final A attachment;
+
+        private volatile T result = null;
+        private volatile Throwable throwable = null;
+        private CountDownLatch completionLatch = new CountDownLatch(1);
+
+        public WrapperFuture() {
+            this(null, null);
+        }
+
+        public WrapperFuture(CompletionHandler<T,A> handler, A attachment) {
+            this.handler = handler;
+            this.attachment = attachment;
+        }
+
+        public void complete(T result) {
+            this.result = result;
+            completionLatch.countDown();
+            if (handler != null) {
+                handler.completed(result, attachment);
+            }
+        }
+
+        public void fail(Throwable t) {
+            throwable = t;
+            completionLatch.countDown();
+            if (handler != null) {
+                handler.failed(throwable, attachment);
+            }
+        }
+
+        @Override
+        public final boolean cancel(boolean mayInterruptIfRunning) {
+            // Could support cancellation by closing the connection
+            return false;
+        }
+
+        @Override
+        public final boolean isCancelled() {
+            // Could support cancellation by closing the connection
+            return false;
+        }
+
+        @Override
+        public final boolean isDone() {
+            return completionLatch.getCount() > 0;
+        }
+
+        @Override
+        public T get() throws InterruptedException, ExecutionException {
+            completionLatch.await();
+            if (throwable != null) {
+                throw new ExecutionException(throwable);
+            }
+            return result;
+        }
+
+        @Override
+        public T get(long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException,
+                TimeoutException {
+            completionLatch.await(timeout, unit);
+            if (throwable != null) {
+                throw new ExecutionException(throwable);
+            }
+            return result;
+        }
+    }
+
+    private static final class LongToIntegerFuture implements Future<Integer> {
+
+        private final Future<Long> wrapped;
+
+        public LongToIntegerFuture(Future<Long> wrapped) {
+            this.wrapped = wrapped;
+        }
+
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            return wrapped.cancel(mayInterruptIfRunning);
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return wrapped.isCancelled();
+        }
+
+        @Override
+        public boolean isDone() {
+            return wrapped.isDone();
+        }
+
+        @Override
+        public Integer get() throws InterruptedException, ExecutionException {
+            Long result = wrapped.get();
+            if (result.longValue() > Integer.MAX_VALUE) {
+                // TODO
+                throw new ExecutionException("Result too big", null);
+            }
+            return new Integer(result.intValue());
+        }
+
+        @Override
+        public Integer get(long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException,
+                TimeoutException {
+            Long result = wrapped.get(timeout, unit);
+            if (result.longValue() > Integer.MAX_VALUE) {
+                // TODO
+                throw new ExecutionException("Result too big", null);
+            }
+            return new Integer(result.intValue());
+        }
     }
 }

Modified: 
tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java 
(original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java Tue 
Mar 26 21:17:18 2013
@@ -16,13 +16,17 @@
  */
 package org.apache.tomcat.websocket;
 
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.net.URI;
 import java.nio.ByteBuffer;
 import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.charset.Charset;
+import java.security.KeyStore;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -38,6 +42,10 @@ import java.util.concurrent.ConcurrentHa
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
 import javax.websocket.ClientEndpoint;
 import javax.websocket.ClientEndpointConfig;
 import javax.websocket.ClientEndpointConfig.Builder;
@@ -57,6 +65,19 @@ import org.apache.tomcat.websocket.pojo.
 public class WsWebSocketContainer
         implements WebSocketContainer, BackgroundProcess {
 
+    /**
+     * Property name to set to configure the value that is passed to
+     * {@link SSLEngine#setEnabledProtocols(String[])}. The value should be a
+     * comma separated string.
+     */
+    public static final String SSL_PROTOCOLS_PROPERTY =
+            "org.apache.tomcat.websocket.SSL_PROTOCOLS";
+    public static final String SSL_TRUSTSTORE_PROPERTY =
+            "org.apache.tomcat.websocket.SSL_TRUSTSTORE";
+    public static final String SSL_TRUSTSTORE_PWD_PROPERTY =
+            "org.apache.tomcat.websocket.SSL_TRUSTSTORE_PWD";
+    public static final String SSL_TRUSTSTORE_PWD_DEFAULT = "changeit";
+
     private static final StringManager sm =
             StringManager.getManager(Constants.PACKAGE_NAME);
     private static final Random random = new Random();
@@ -202,6 +223,9 @@ public class WsWebSocketContainer
                         sm.getString("wsWebSocketContainer.invalidScheme"));
             }
         } else {
+            if ("wss".equalsIgnoreCase(scheme)) {
+                secure = true;
+            }
             sa = new InetSocketAddress(host, port);
         }
 
@@ -216,16 +240,21 @@ public class WsWebSocketContainer
 
         AsyncChannelWrapper channel;
         if (secure) {
-            channel = new AsyncChannelWrapperSecure(socketChannel);
+            SSLEngine sslEngine = createSSLEngine(
+                    clientEndpointConfiguration.getUserProperties());
+            channel = new AsyncChannelWrapperSecure(socketChannel, sslEngine);
         } else {
             channel = new AsyncChannelWrapperNonSecure(socketChannel);
         }
 
-
         ByteBuffer response;
         String subProtocol;
         try {
             fConnect.get();
+
+            Future<Void> fHandshake = channel.handshake();
+            fHandshake.get();
+
             int toWrite = request.limit();
 
             Future<Integer> fWrite = channel.write(request);
@@ -256,7 +285,7 @@ public class WsWebSocketContainer
                 throw new DeploymentException(
                         sm.getString("Sec-WebSocket-Protocol"));
             }
-        } catch (ExecutionException | InterruptedException e) {
+        } catch (ExecutionException | InterruptedException | SSLException e) {
             throw new DeploymentException(
                     sm.getString("wsWebSocketContainer.httpRequestFailed"), e);
         }
@@ -524,6 +553,55 @@ public class WsWebSocketContainer
     }
 
 
+    private SSLEngine createSSLEngine(Map<String,Object> userProperties)
+            throws DeploymentException {
+
+        try {
+            // Create the SSL Context
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+
+            // Trust store
+            String sslTrustStoreValue =
+                    (String) userProperties.get(SSL_TRUSTSTORE_PROPERTY);
+            if (sslTrustStoreValue != null) {
+                String sslTrustStorePwdValue = (String) userProperties.get(
+                        SSL_TRUSTSTORE_PWD_PROPERTY);
+                if (sslTrustStorePwdValue == null) {
+                    sslTrustStorePwdValue = SSL_TRUSTSTORE_PWD_DEFAULT;
+                }
+
+                File keyStoreFile = new File(sslTrustStoreValue);
+                KeyStore ks = KeyStore.getInstance("JKS");
+                try (InputStream is = new FileInputStream(keyStoreFile)) {
+                    ks.load(is, sslTrustStorePwdValue.toCharArray());
+                }
+
+                TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+                        TrustManagerFactory.getDefaultAlgorithm());
+                tmf.init(ks);
+
+                sslContext.init(null, tmf.getTrustManagers(), null);
+            } else {
+                sslContext.init(null, null, null);
+            }
+
+            SSLEngine engine = sslContext.createSSLEngine();
+
+            String sslProtocolsValue =
+                    (String) userProperties.get(SSL_PROTOCOLS_PROPERTY);
+            if (sslProtocolsValue != null) {
+                engine.setEnabledProtocols(sslProtocolsValue.split(","));
+            }
+
+            engine.setUseClientMode(true);
+
+            return engine;
+        } catch (Exception e) {
+            throw new DeploymentException("TODO", e);
+        }
+    }
+
+
     @Override
     public long getDefaultMaxSessionIdleTimeout() {
         return defaultMaxSessionIdleTimeout;

Modified: tomcat/trunk/test/org/apache/tomcat/util/net/TesterSupport.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/util/net/TesterSupport.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/tomcat/util/net/TesterSupport.java (original)
+++ tomcat/trunk/test/org/apache/tomcat/util/net/TesterSupport.java Tue Mar 26 
21:17:18 2013
@@ -77,7 +77,7 @@ public final class TesterSupport {
         RFC_5746_SUPPORTED = result;
     }
 
-    protected static void initSsl(Tomcat tomcat) {
+    public static void initSsl(Tomcat tomcat) {
         initSsl(tomcat, "localhost.jks", null, null);
     }
 

Modified: 
tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java 
(original)
+++ tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java 
Tue Mar 26 21:17:18 2013
@@ -46,6 +46,7 @@ import org.apache.catalina.Context;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
 import org.apache.coyote.http11.Http11Protocol;
+import org.apache.tomcat.util.net.TesterSupport;
 import org.apache.tomcat.websocket.TesterSingleMessageClient.BasicBinary;
 import org.apache.tomcat.websocket.TesterSingleMessageClient.BasicHandler;
 import org.apache.tomcat.websocket.TesterSingleMessageClient.BasicText;
@@ -710,4 +711,43 @@ public class TestWsWebSocketContainer ex
         }
     }
 
+
+    @Test
+    public void testConnectToServerEndpointSSL() throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+        ctx.addApplicationListener(TesterEchoServer.Config.class.getName());
+
+        TesterSupport.initSsl(tomcat);
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer =
+                ContainerProvider.getWebSocketContainer();
+        ClientEndpointConfig clientEndpointConfig =
+                ClientEndpointConfig.Builder.create().build();
+        clientEndpointConfig.getUserProperties().put(
+                WsWebSocketContainer.SSL_TRUSTSTORE_PROPERTY,
+                "test/org/apache/tomcat/util/net/ca.jks");
+        Session wsSession = wsContainer.connectToServer(
+                TesterProgrammaticEndpoint.class,
+                clientEndpointConfig,
+                new URI("wss://localhost:" + getPort() +
+                        TesterEchoServer.Config.PATH_ASYNC));
+        CountDownLatch latch = new CountDownLatch(1);
+        BasicText handler = new BasicText(latch);
+        wsSession.addMessageHandler(handler);
+        wsSession.getBasicRemote().sendText(MESSAGE_STRING_1);
+
+        boolean latchResult = handler.getLatch().await(10, TimeUnit.SECONDS);
+
+        Assert.assertTrue(latchResult);
+
+        List<String> messages = handler.getMessages();
+        Assert.assertEquals(1, messages.size());
+        Assert.assertEquals(MESSAGE_STRING_1, messages.get(0));
+    }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to