changeset 878:e5cda64075ed

Add TLS support to command channel. This patch adds TLS support for the command channel. SSL for cmd-channel communication is only used if things have been configured that way in $THERMOSTAT_HOME/etc/ssl.properties. The reason as to why this has been done is because if this is always on, users would need to set up a proper server (i.e. agent) side keystore which is used for serving SSL enabled communication. An alternative would be to ship with a default keystore. However, this defeats the purpose of securing the channel in the first place. The world would have access to this keystore. That's why we've opted for default == false. Note that these defaults can be changed by packagers if they so desire. If COMMAND_CHANNEL_USE_SSL=true then appropriate server and client SSL contexts are created and passed to netty's SSLHandler. After a connection to the agent was successfully established an SSL handshake is initiated (if so configured). Tests have been added for most (all?) things I've added for SSL-enablement of the command channel. Reviewed-by: vanaltj Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-December/004770.html PR1239
author Severin Gehwolf <sgehwolf@redhat.com>
date Tue, 18 Dec 2012 12:32:36 +0100
parents ec6d1e7c6fed
children 59738c25578b
files agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerContext.java agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerImpl.java agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ServerHandler.java agent/command/src/test/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerContextTest.java agent/command/src/test/java/com/redhat/thermostat/agent/command/internal/ServerHandlerTest.java client/command/pom.xml client/command/src/main/java/com/redhat/thermostat/client/command/internal/ConfigurationRequestContext.java client/command/src/main/java/com/redhat/thermostat/client/command/internal/RequestQueueImpl.java client/command/src/main/java/com/redhat/thermostat/client/command/internal/ResponseHandler.java client/command/src/test/java/com/redhat/thermostat/client/command/internal/ConfigurationRequestContextTest.java common/core/src/main/java/com/redhat/thermostat/common/internal/CustomX509TrustManager.java common/core/src/main/java/com/redhat/thermostat/common/internal/SSLKeystoreConfiguration.java common/core/src/main/java/com/redhat/thermostat/common/ssl/SSLContextFactory.java common/core/src/main/java/com/redhat/thermostat/common/ssl/SSLKeystoreConfiguration.java common/core/src/test/java/com/redhat/thermostat/common/internal/SSLKeystoreConfigurationTest.java common/core/src/test/java/com/redhat/thermostat/common/ssl/SSLContextFactoryTest.java common/core/src/test/java/com/redhat/thermostat/common/ssl/SSLKeystoreConfigurationTest.java common/core/src/test/resources/client.properties common/core/src/test/resources/cmdChanServer.keystore common/core/src/test/resources/ssl.properties distribution/config/ssl.properties
diffstat 21 files changed, 854 insertions(+), 198 deletions(-) [+]
line wrap: on
line diff
--- a/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerContext.java	Tue Dec 18 16:39:38 2012 -0500
+++ b/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerContext.java	Tue Dec 18 12:32:36 2012 +0100
@@ -37,6 +37,10 @@
 package com.redhat.thermostat.agent.command.internal;
 
 import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLEngine;
 
 import org.jboss.netty.bootstrap.Bootstrap;
 import org.jboss.netty.bootstrap.ServerBootstrap;
@@ -46,13 +50,21 @@
 import org.jboss.netty.channel.group.ChannelGroup;
 import org.jboss.netty.channel.group.DefaultChannelGroup;
 import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.jboss.netty.handler.ssl.SslHandler;
 import org.osgi.framework.BundleContext;
 
 import com.redhat.thermostat.agent.command.ReceiverRegistry;
 import com.redhat.thermostat.common.command.ConfigurationCommandContext;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.ssl.SSLContextFactory;
+import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration;
+import com.redhat.thermostat.common.ssl.SslInitException;
+import com.redhat.thermostat.common.utils.LoggingUtils;
 
 class ConfigurationServerContext implements ConfigurationCommandContext {
 
+    private static final Logger logger = LoggingUtils.getLogger(ConfigurationServerContext.class);
+    
     private final ServerBootstrap bootstrap;
     private final ChannelGroup channels;
     private final BundleContext context;
@@ -95,6 +107,18 @@
         @Override
         public ChannelPipeline getPipeline() throws Exception {
             ChannelPipeline pipeline = Channels.pipeline();
+            if (SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()) {
+                SSLEngine engine = null;
+                try {
+                    engine = SSLContextFactory.getServerContext()
+                            .createSSLEngine();
+                    engine.setUseClientMode(false);
+                } catch (SslInitException | InvalidConfigurationException e) {
+                    logger.log(Level.SEVERE,
+                            "Failed to initiate command channel endpoint", e);
+                }
+                pipeline.addLast("ssl", new SslHandler(engine));
+            }
             pipeline.addLast("decoder", new RequestDecoder());
             pipeline.addLast("encoder", new ResponseEncoder());
             pipeline.addLast("handler", new ServerHandler(new ReceiverRegistry(context)));
--- a/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerImpl.java	Tue Dec 18 16:39:38 2012 -0500
+++ b/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerImpl.java	Tue Dec 18 12:32:36 2012 +0100
@@ -37,14 +37,18 @@
 package com.redhat.thermostat.agent.command.internal;
 
 import java.net.InetSocketAddress;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import org.jboss.netty.bootstrap.ServerBootstrap;
 
 import com.redhat.thermostat.agent.command.ConfigurationServer;
+import com.redhat.thermostat.common.utils.LoggingUtils;
 
 class ConfigurationServerImpl implements ConfigurationServer {
 
     private final ConfigurationServerContext ctx;
+    private static final Logger logger = LoggingUtils.getLogger(ConfigurationServerImpl.class);
 
     ConfigurationServerImpl(ConfigurationServerContext ctx) {
         this.ctx = ctx;
@@ -55,13 +59,16 @@
         ServerBootstrap bootstrap = (ServerBootstrap) ctx.getBootstrap();
 
         String [] host = address.split(":");
+        InetSocketAddress addr = new InetSocketAddress(host[0], Integer.parseInt(host[1]));
         
+        logger.log(Level.FINE, "Starting command channel server on " + addr.toString());
         // Bind and start to accept incoming connections.
-        bootstrap.bind(new InetSocketAddress(host[0], Integer.parseInt(host[1])));
+        bootstrap.bind(addr);
     }
 
     @Override
     public void stopListening() {
+        logger.log(Level.FINE, "Stopping command channel server");
         ctx.getChannelGroup().close().awaitUninterruptibly();
         ctx.getChannelGroup().clear();
         ctx.getBootstrap().releaseExternalResources();
--- a/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ServerHandler.java	Tue Dec 18 16:39:38 2012 -0500
+++ b/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ServerHandler.java	Tue Dec 18 12:32:36 2012 +0100
@@ -41,12 +41,15 @@
 
 import org.apache.commons.codec.binary.Base64;
 import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelEvent;
 import org.jboss.netty.channel.ChannelFuture;
 import org.jboss.netty.channel.ChannelFutureListener;
 import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
 import org.jboss.netty.channel.ExceptionEvent;
 import org.jboss.netty.channel.MessageEvent;
-import org.jboss.netty.channel.SimpleChannelHandler;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+import org.jboss.netty.handler.ssl.SslHandler;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
@@ -56,21 +59,45 @@
 import com.redhat.thermostat.common.command.Request;
 import com.redhat.thermostat.common.command.Response;
 import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.core.AuthToken;
 import com.redhat.thermostat.storage.core.SecureStorage;
 import com.redhat.thermostat.storage.core.Storage;
 
-class ServerHandler extends SimpleChannelHandler {
+class ServerHandler extends SimpleChannelUpstreamHandler {
 
+    private static final Logger logger = LoggingUtils.getLogger(ServerHandler.class);
     private ReceiverRegistry receivers;
 
     public ServerHandler(ReceiverRegistry receivers) {
         this.receivers = receivers;
     }
 
-    private static final Logger logger = LoggingUtils.getLogger(ServerHandler.class);
+    @Override
+    public void handleUpstream(
+            ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
+        if (e instanceof ChannelStateEvent) {
+            logger.log(Level.FINEST, e.toString());
+        }
+        super.handleUpstream(ctx, e);
+    }
+    
+    @Override
+    public void channelConnected(
+            ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
+        if (SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()) {
+            // Get the SslHandler in the current pipeline.
+            // We added it in ConfigurationServerContext$ServerPipelineFactory.
+            final SslHandler sslHandler = ctx.getPipeline().get(
+                    SslHandler.class);
 
+            // Get notified when SSL handshake is done.
+            ChannelFuture handshakeFuture = sslHandler.handshake();
+            handshakeFuture.addListener(new SSLHandshakeDoneListener());
+        }
+    }
+    
     @Override
     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
         Request request = (Request) e.getMessage();
@@ -123,5 +150,20 @@
         logger.log(Level.WARNING, "Unexpected exception from downstream.", e.getCause());
         e.getChannel().close();
     }
+    
+    /*
+     * Only registered if SSL is enabled
+     */
+    static final class SSLHandshakeDoneListener implements ChannelFutureListener {
 
+        @Override
+        public void operationComplete(ChannelFuture future) throws Exception {
+            if (future.isSuccess()) {
+                logger.log(Level.FINE, "Finished SSL handshake.");
+            } else {
+                logger.log(Level.WARNING, "SSL handshake failed!");
+                future.getChannel().close();
+            }
+        }
+    }
 }
--- a/agent/command/src/test/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerContextTest.java	Tue Dec 18 16:39:38 2012 -0500
+++ b/agent/command/src/test/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerContextTest.java	Tue Dec 18 12:32:36 2012 +0100
@@ -40,21 +40,32 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
 
 import org.jboss.netty.bootstrap.Bootstrap;
 import org.jboss.netty.channel.ChannelHandler;
 import org.jboss.netty.channel.ChannelPipeline;
 import org.jboss.netty.channel.ChannelPipelineFactory;
 import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.handler.ssl.SslHandler;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 import org.osgi.framework.BundleContext;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
 
-import com.redhat.thermostat.agent.command.internal.ConfigurationServerContext;
-import com.redhat.thermostat.agent.command.internal.RequestDecoder;
-import com.redhat.thermostat.agent.command.internal.ResponseEncoder;
-import com.redhat.thermostat.agent.command.internal.ServerHandler;
+import com.redhat.thermostat.common.ssl.SSLContextFactory;
+import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration;
 
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ SSLKeystoreConfiguration.class, SSLContextFactory.class,
+        SSLEngine.class, SSLContext.class })
 public class ConfigurationServerContextTest {
 
     ConfigurationServerContext ctx;
@@ -83,7 +94,7 @@
         try {
             p = pf.getPipeline();
         } catch (Exception e) {
-            // Do nothing
+            e.printStackTrace();
         }
         assertNotNull(p);
 
@@ -99,6 +110,37 @@
         assertNotNull(handler);
         assertTrue(handler instanceof ServerHandler);
     }
+    
+    @Test
+    public void testBootstrapSSL() throws Exception {
+        PowerMockito.mockStatic(SSLKeystoreConfiguration.class);
+        when(SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()).thenReturn(true);
+        PowerMockito.mockStatic(SSLContextFactory.class);
+        // SSL classes need to be mocked with PowerMockito
+        SSLContext context = PowerMockito.mock(SSLContext.class);
+        when(SSLContextFactory.getServerContext()).thenReturn(context);
+        SSLEngine engine = PowerMockito.mock(SSLEngine.class);
+        when(context.createSSLEngine()).thenReturn(engine);
+        
+        Bootstrap bootstrap = ctx.getBootstrap();
+        assertNotNull(bootstrap);
+
+        ChannelPipelineFactory pf = bootstrap.getPipelineFactory();
+        assertNotNull(pf);
+
+        ChannelPipeline p = null;
+        try {
+            p = pf.getPipeline();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        assertNotNull(p);
+
+        ChannelHandler encoder = p.get("ssl");
+        assertNotNull(encoder);
+        assertTrue(encoder instanceof SslHandler);
+        Mockito.verify(engine).setUseClientMode(false);
+    }
 
     @Test
     public void testChannelGroup() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/command/src/test/java/com/redhat/thermostat/agent/command/internal/ServerHandlerTest.java	Tue Dec 18 12:32:36 2012 +0100
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.agent.command.internal;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.handler.ssl.SslHandler;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.redhat.thermostat.agent.command.internal.ServerHandler.SSLHandshakeDoneListener;
+import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ SSLKeystoreConfiguration.class})
+public class ServerHandlerTest {
+
+    @Test
+    public void channelConnectedAddsSSLListener() throws Exception {
+        ServerHandler handler = new ServerHandler(null);
+        
+        // enable ssl
+        PowerMockito.mockStatic(SSLKeystoreConfiguration.class);
+        when(SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()).thenReturn(true);
+        
+        ChannelHandlerContext ctx = mock(ChannelHandlerContext.class);
+        ChannelPipeline pipeline = mock(ChannelPipeline.class);
+        when(ctx.getPipeline()).thenReturn(pipeline);
+        SslHandler sslHandler = mock(SslHandler.class);
+        when(pipeline.get(SslHandler.class)).thenReturn(sslHandler);
+        ChannelFuture future = mock(ChannelFuture.class);
+        when(sslHandler.handshake()).thenReturn(future);
+        
+        handler.channelConnected(ctx, null);
+        verify(future).addListener(any(SSLHandshakeDoneListener.class));
+    }
+}
--- a/client/command/pom.xml	Tue Dec 18 16:39:38 2012 -0500
+++ b/client/command/pom.xml	Tue Dec 18 12:32:36 2012 +0100
@@ -62,6 +62,16 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-api-mockito</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-module-junit4</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.jboss.netty</groupId>
       <artifactId>netty</artifactId>
     </dependency>
--- a/client/command/src/main/java/com/redhat/thermostat/client/command/internal/ConfigurationRequestContext.java	Tue Dec 18 16:39:38 2012 -0500
+++ b/client/command/src/main/java/com/redhat/thermostat/client/command/internal/ConfigurationRequestContext.java	Tue Dec 18 12:32:36 2012 +0100
@@ -38,14 +38,19 @@
 
 import java.util.concurrent.Executors;
 
+import javax.net.ssl.SSLEngine;
+
 import org.jboss.netty.bootstrap.Bootstrap;
 import org.jboss.netty.bootstrap.ClientBootstrap;
 import org.jboss.netty.channel.ChannelPipeline;
 import org.jboss.netty.channel.ChannelPipelineFactory;
 import org.jboss.netty.channel.Channels;
 import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
+import org.jboss.netty.handler.ssl.SslHandler;
 
 import com.redhat.thermostat.common.command.ConfigurationCommandContext;
+import com.redhat.thermostat.common.ssl.SSLContextFactory;
+import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration;
 
 public class ConfigurationRequestContext implements ConfigurationCommandContext {
 
@@ -84,6 +89,12 @@
         @Override
         public ChannelPipeline getPipeline() throws Exception {
             ChannelPipeline pipeline = Channels.pipeline();
+            if (SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()) {
+                SSLEngine engine = SSLContextFactory.getClientContext()
+                        .createSSLEngine();
+                engine.setUseClientMode(true);
+                pipeline.addLast("ssl", new SslHandler(engine));
+            }
             pipeline.addLast("decoder", new ResponseDecoder());
             pipeline.addLast("encoder", new RequestEncoder());
             return pipeline;
--- a/client/command/src/main/java/com/redhat/thermostat/client/command/internal/RequestQueueImpl.java	Tue Dec 18 16:39:38 2012 -0500
+++ b/client/command/src/main/java/com/redhat/thermostat/client/command/internal/RequestQueueImpl.java	Tue Dec 18 12:32:36 2012 +0100
@@ -38,11 +38,16 @@
 
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import org.apache.commons.codec.binary.Base64;
 import org.jboss.netty.bootstrap.ClientBootstrap;
 import org.jboss.netty.channel.Channel;
 import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.ChannelFutureListener;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.handler.ssl.SslHandler;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
@@ -52,6 +57,8 @@
 import com.redhat.thermostat.common.command.RequestResponseListener;
 import com.redhat.thermostat.common.command.Response;
 import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration;
+import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.core.AuthToken;
 import com.redhat.thermostat.storage.core.SecureStorage;
 import com.redhat.thermostat.storage.core.Storage;
@@ -59,6 +66,7 @@
 
 class RequestQueueImpl implements RequestQueue {
 
+    private static final Logger logger = LoggingUtils.getLogger(RequestQueueImpl.class);
     private final BlockingQueue<Request> queue;
     private final ConfigurationRequestContext ctx;
     private volatile boolean processing;
@@ -136,7 +144,11 @@
                 f.awaitUninterruptibly();
                 if (f.isSuccess()) {
                 	Channel c = f.getChannel();
-                	c.getPipeline().addLast("responseHandler", new ResponseHandler(request));
+                	ChannelPipeline pipeline = c.getPipeline();
+                	if (SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()) {
+                	    doSSLHandShake(pipeline, request);
+                	}
+                	pipeline.addLast("responseHandler", new ResponseHandler(request));
                 	c.write(request);
                 } else {
                 	Response response  = new Response(ResponseType.ERROR);
@@ -146,11 +158,44 @@
         }
 
     }
-
+    
     private void fireComplete(Request request, Response response) {
         // TODO add more information once Response supports parameters.
         for (RequestResponseListener listener : request.getListeners()) {
             listener.fireComplete(request, response);
         }
     }
+
+    private void doSSLHandShake(ChannelPipeline pipeline, Request request) {
+        // Get the SslHandler from the pipeline
+        // which was added in ConfigurationRequestContext$ClientPipelineFactory
+        SslHandler sslHandler = pipeline.get(SslHandler.class);
+        
+        logger.log(Level.FINE, "Starting SSL handshake");
+        // Begin handshake.
+        ChannelFuture future = sslHandler.handshake();
+        
+        // Register a future listener, since it gives us a way to
+        // report an error on client side.
+        future.addListener(new SSLErrorReporter(request));
+    }
+
+    private class SSLErrorReporter implements ChannelFutureListener {
+        
+        private final Request request;
+        
+        private SSLErrorReporter(Request request) {
+            this.request = request;
+        }
+        
+        @Override
+        public void operationComplete(ChannelFuture future) throws Exception {
+            if (!future.isSuccess()) {
+                logger.log(Level.WARNING,
+                        "SSL handshake failed check agent logs for details!",
+                        future.getCause());
+                fireComplete(request, new Response(ResponseType.ERROR));
+            }
+        }
+    }
 }
--- a/client/command/src/main/java/com/redhat/thermostat/client/command/internal/ResponseHandler.java	Tue Dec 18 16:39:38 2012 -0500
+++ b/client/command/src/main/java/com/redhat/thermostat/client/command/internal/ResponseHandler.java	Tue Dec 18 12:32:36 2012 +0100
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.client.command.internal;
 
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import org.jboss.netty.channel.ChannelHandlerContext;
@@ -72,6 +73,7 @@
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
         // TODO when response has support for parameters, provide the exception as well.
+        logger.log(Level.WARNING, "exception caught: ", e.getCause());
         Response response = new Response(ResponseType.ERROR);
         notifyListeners(response);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/command/src/test/java/com/redhat/thermostat/client/command/internal/ConfigurationRequestContextTest.java	Tue Dec 18 12:32:36 2012 +0100
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.command.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+
+import org.jboss.netty.bootstrap.Bootstrap;
+import org.jboss.netty.channel.ChannelHandler;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.handler.ssl.SslHandler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.redhat.thermostat.common.ssl.SSLContextFactory;
+import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ SSLKeystoreConfiguration.class, SSLContextFactory.class,
+        SSLContext.class, SSLEngine.class })
+public class ConfigurationRequestContextTest {
+
+    ConfigurationRequestContext ctx;
+
+    @Before
+    public void setUp() {
+        ctx = new ConfigurationRequestContext();
+    }
+
+    @After
+    public void tearDown() {
+        ctx = null;
+    }
+
+    @Test
+    public void testSSLHandlersAdded() throws Exception {
+        PowerMockito.mockStatic(SSLKeystoreConfiguration.class);
+        when(SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()).thenReturn(
+                true);
+        PowerMockito.mockStatic(SSLContextFactory.class);
+        // SSL classes need to be mocked with PowerMockito
+        SSLContext context = PowerMockito.mock(SSLContext.class);
+        when(SSLContextFactory.getClientContext()).thenReturn(context);
+        SSLEngine engine = PowerMockito.mock(SSLEngine.class);
+        when(context.createSSLEngine()).thenReturn(engine);
+
+        Bootstrap bootstrap = ctx.getBootstrap();
+        assertNotNull(bootstrap);
+
+        ChannelPipelineFactory pf = bootstrap.getPipelineFactory();
+        assertNotNull(pf);
+
+        ChannelPipeline p = null;
+        try {
+            p = pf.getPipeline();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        assertNotNull(p);
+
+        ChannelHandler sslHandler = p.get("ssl");
+        assertNotNull(sslHandler);
+        assertTrue(sslHandler instanceof SslHandler);
+        Mockito.verify(engine).setUseClientMode(true);
+    }
+
+    @Test
+    public void testBootstrapAndHandlers() {
+        Bootstrap bootstrap = ctx.getBootstrap();
+        assertNotNull(bootstrap);
+
+        assertTrue((Boolean) bootstrap.getOption("tcpNoDelay"));
+        assertTrue((Boolean) bootstrap.getOption("keepAlive"));
+        assertTrue((Boolean) bootstrap.getOption("reuseAddress"));
+        assertEquals(100, bootstrap.getOption("connectTimeoutMillis"));
+        assertTrue((Boolean) bootstrap.getOption("readWriteFair"));
+
+        ChannelPipelineFactory pf = bootstrap.getPipelineFactory();
+        assertNotNull(pf);
+
+        ChannelPipeline p = null;
+        try {
+            p = pf.getPipeline();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        assertNotNull(p);
+
+        ChannelHandler encoder = p.get("encoder");
+        assertNotNull(encoder);
+        assertTrue(encoder instanceof RequestEncoder);
+
+        ChannelHandler decoder = p.get("decoder");
+        assertNotNull(decoder);
+        assertTrue(decoder instanceof ResponseDecoder);
+    }
+}
--- a/common/core/src/main/java/com/redhat/thermostat/common/internal/CustomX509TrustManager.java	Tue Dec 18 16:39:38 2012 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/internal/CustomX509TrustManager.java	Tue Dec 18 12:32:36 2012 +0100
@@ -53,6 +53,7 @@
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
 
+import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 
 /**
--- a/common/core/src/main/java/com/redhat/thermostat/common/internal/SSLKeystoreConfiguration.java	Tue Dec 18 16:39:38 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.common.internal;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Properties;
-
-import com.redhat.thermostat.common.config.ConfigUtils;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-
-public class SSLKeystoreConfiguration {
-
-    private static Properties clientProps = null;
-    private static final String KEYSTORE_FILE_KEY = "KEYSTORE_FILE";
-    private static final String KEYSTORE_FILE_PWD_KEY = "KEYSTORE_PASSWORD";
-
-    /**
-     * 
-     * @return The keystore file as specified in
-     *         $THERMOSTAT_HOME/ssl.properties if any. null otherwise.
-     */
-    public static File getKeystoreFile() {
-        try {
-            loadClientProperties();
-        } catch (InvalidConfigurationException e) {
-            // Thermostat home not set? Should have failed earlier. Do something
-            // reasonable.
-            return null;
-        }
-        String path = clientProps.getProperty(KEYSTORE_FILE_KEY);
-        if (path != null) {
-            File file = new File(path);
-            return file;
-        }
-        return null;
-    }
-
-    /**
-     * 
-     * @return The keystore file as specified in
-     *         $THERMOSTAT_HOME/ssl.properties if any. The empty string
-     *         otherwise.
-     * @throws InvalidConfigurationException
-     */
-    public static String getKeyStorePassword() {
-        try {
-            loadClientProperties();
-        } catch (InvalidConfigurationException e) {
-            // Thermostat home not set? Do something reasonable
-            return "";
-        }
-        String pwd = clientProps.getProperty(KEYSTORE_FILE_PWD_KEY);
-        if (pwd == null) {
-            return "";
-        } else {
-            return pwd;
-        }
-    }
-
-    // testing hook
-    static void initClientProperties(File clientPropertiesFile) {
-        clientProps = new Properties();
-        try {
-            clientProps.load(new FileInputStream(clientPropertiesFile));
-        } catch (IOException | IllegalArgumentException e) {
-            // Could not load ssl properties file. This is fine as it's
-            // an optional config.
-        }
-    }
-
-    private static void loadClientProperties()
-            throws InvalidConfigurationException {
-        if (clientProps == null) {
-            File thermostatEtcDir = new File(ConfigUtils.getThermostatHome(),
-                    "etc");
-            File clientPropertiesFile = new File(thermostatEtcDir,
-                    "ssl.properties");
-            initClientProperties(clientPropertiesFile);
-        }
-    }
-}
--- a/common/core/src/main/java/com/redhat/thermostat/common/ssl/SSLContextFactory.java	Tue Dec 18 16:39:38 2012 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/ssl/SSLContextFactory.java	Tue Dec 18 12:32:36 2012 +0100
@@ -36,23 +36,50 @@
 
 package com.redhat.thermostat.common.ssl;
 
+import java.io.File;
+import java.security.GeneralSecurityException;
 import java.security.KeyManagementException;
+import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
+import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
 
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.internal.KeyStoreProvider;
 import com.redhat.thermostat.common.internal.TrustManagerFactory;
+import com.redhat.thermostat.common.utils.LoggingUtils;
 
 public class SSLContextFactory {
 
+    private static final Logger logger = LoggingUtils.getLogger(SSLContextFactory.class);
     private static final String PROTOCOL = "TLS";
+    private static final String ALGORITHM = "SunX509";
+    private static SSLContext serverContext;
     private static SSLContext clientContext;
+    
+    /**
+     * 
+     * @return An initialized SSLContext 
+     * @throws SslInitException
+     * @throws InvalidConfigurationException
+     */
+    public static SSLContext getServerContext() throws SslInitException,
+            InvalidConfigurationException {
+        if (serverContext != null) {
+            return serverContext;
+        }
+        initServerContext();
+        return serverContext;
+    }
 
     /**
      * 
-     * @return An initialized SSLContext with Thermostat's own X509TrustManager
+     * @return An initialized SSLContext with Thermostat's only X509TrustManager
      *         registered.
      * @throws SslInitException if SSL initialization failed.
      */
@@ -76,4 +103,46 @@
         }
         clientContext = clientCtxt;
     }
+
+    private static void initServerContext() throws SslInitException,
+            InvalidConfigurationException {
+        SSLContext serverCtxt = null;
+        File trustStoreFile = SSLKeystoreConfiguration.getKeystoreFile();
+        String keyStorePassword = SSLKeystoreConfiguration
+                .getKeyStorePassword();
+        KeyStore ks = KeyStoreProvider.getKeyStore(trustStoreFile,
+                keyStorePassword);
+        if (ks == null) {
+            // This is bad news. We need a proper key store for retrieving the
+            // server certificate.
+            logReason(trustStoreFile);
+            throw new SslInitException(
+                    "Failed to initialize server side SSL context");
+        }
+        try {
+            // Set up key manager factory to use our key store
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(ALGORITHM);
+            String keystorePassword = SSLKeystoreConfiguration.getKeyStorePassword();
+            kmf.init(ks, keystorePassword.toCharArray());
+
+            // Initialize the SSLContext to work with our key managers.
+            serverCtxt = SSLContext.getInstance(PROTOCOL);
+            serverCtxt.init(kmf.getKeyManagers(), null, new SecureRandom());
+        } catch (GeneralSecurityException e) {
+            throw new SslInitException(e);
+        }
+        serverContext = serverCtxt;
+    }
+
+    private static void logReason(File trustStoreFile) {
+        String detail = "Reason: no keystore file specified!";
+        if (trustStoreFile != null) {
+            if (!trustStoreFile.exists()) {
+                detail = "Reason: keystore file '" + trustStoreFile.toString() + "' does not exist!";
+            } else {
+                detail = "Reason: illegal keystore password!";
+            }
+        }
+        logger.log(Level.SEVERE, "Failed to load keystore. " + detail);
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/ssl/SSLKeystoreConfiguration.java	Tue Dec 18 12:32:36 2012 +0100
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.ssl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import com.redhat.thermostat.common.config.ConfigUtils;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+
+public class SSLKeystoreConfiguration {
+
+    private static Properties clientProps = null;
+    private static final String KEYSTORE_FILE_KEY = "KEYSTORE_FILE";
+    private static final String KEYSTORE_FILE_PWD_KEY = "KEYSTORE_PASSWORD";
+    private static final String CMD_CHANNEL_SSL_KEY = "COMMAND_CHANNEL_USE_SSL";
+
+    /**
+     * 
+     * @return The keystore file as specified in $THERMOSTAT_HOME/etc/ssl.properties
+     *         if any. null otherwise.
+     */
+    public static File getKeystoreFile() {
+        try {
+            loadClientProperties();
+        } catch (InvalidConfigurationException e) {
+            // Thermostat home not set? Should have failed earlier. Do something
+            // reasonable.
+            return null;
+        }
+        String path = clientProps.getProperty(KEYSTORE_FILE_KEY);
+        if (path != null) {
+            File file = new File(path);
+            return file;
+        }
+        return null;
+    }
+
+    /**
+     * 
+     * @return The keystore file as specified in $THERMOSTAT_HOME/etc/ssl.properties
+     *         if any. The empty string otherwise.
+     */
+    public static String getKeyStorePassword() {
+        try {
+            loadClientProperties();
+        } catch (InvalidConfigurationException e) {
+            // Thermostat home not set? Do something reasonable
+            return "";
+        }
+        String pwd = clientProps.getProperty(KEYSTORE_FILE_PWD_KEY);
+        if (pwd == null) {
+            return "";
+        } else {
+            return pwd;
+        }
+    }
+    
+    /**
+     * 
+     * @return true if and only if SSL should be enabled for command channel
+     *         communication between agent and client. I.e. if
+     *         $THERMOSTAT_HOME/etc/ssl.properties exists and proper config has
+     *         been added. false otherwise.
+     */
+    public static boolean shouldSSLEnableCmdChannel() {
+        boolean result = false;
+        try {
+            loadClientProperties();
+        } catch (InvalidConfigurationException e) {
+            // Thermostat home not set? Do something reasonable
+            return result;
+        }
+        String token = clientProps.getProperty(CMD_CHANNEL_SSL_KEY);
+        if (token != null) {
+            result = Boolean.parseBoolean(token);
+        }
+        return result;
+    }
+
+    // testing hook
+    static void initClientProperties(File clientPropertiesFile) {
+        clientProps = new Properties();
+        try {
+            clientProps.load(new FileInputStream(clientPropertiesFile));
+        } catch (IOException | IllegalArgumentException e) {
+            // Could not load ssl properties file. This is fine as it's
+            // an optional config.
+        }
+    }
+
+    private static void loadClientProperties()
+            throws InvalidConfigurationException {
+        if (clientProps == null) {
+            File thermostatEtcDir = new File(ConfigUtils.getThermostatHome(),
+                    "etc");
+            File clientPropertiesFile = new File(thermostatEtcDir,
+                    "ssl.properties");
+            initClientProperties(clientPropertiesFile);
+        }
+    }
+}
--- a/common/core/src/test/java/com/redhat/thermostat/common/internal/SSLKeystoreConfigurationTest.java	Tue Dec 18 16:39:38 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.common.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.common.internal.SSLKeystoreConfiguration;
-
-public class SSLKeystoreConfigurationTest {
-
-    @Test
-    public void canGetKeystoreFileFromProps() throws Exception {
-        File clientProps = new File(this.getClass().getResource("/client.properties").getFile());
-        SSLKeystoreConfiguration.initClientProperties(clientProps);
-        String keystorePath = "/path/to/thermostat.keystore";
-        String keystorePwd = "some password";
-        assertEquals(keystorePath, SSLKeystoreConfiguration.getKeystoreFile().getAbsolutePath());
-        assertEquals(keystorePwd, SSLKeystoreConfiguration.getKeyStorePassword());
-    }
-    
-    @Test
-    public void notExistingPropertiesFileReturnsNull() throws Exception {
-        File clientProps = new File("i/am/not/there/file.txt");
-        SSLKeystoreConfiguration.initClientProperties(clientProps);
-        assertTrue(SSLKeystoreConfiguration.getKeystoreFile() == null);
-        assertEquals("", SSLKeystoreConfiguration.getKeyStorePassword());
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/test/java/com/redhat/thermostat/common/ssl/SSLContextFactoryTest.java	Tue Dec 18 12:32:36 2012 +0100
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.ssl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ SSLKeystoreConfiguration.class, SSLContext.class,
+        KeyManagerFactory.class })
+public class SSLContextFactoryTest {
+
+    /*
+     * cmdChanServer.keystore is a keystore converted from openssl. It contains
+     * key material which was signed by ca.crt. More information as to how to
+     * create such a file here (first create server.crt => convert it to java
+     * keystore format):
+     * http://icedtea.classpath.org/wiki/Thermostat/DevDeployWarInTomcatNotes
+     * 
+     * Unfortunately, powermock messes up the KeyManagerFactory. We can only
+     * verify that proper methods are called.
+     */
+    @Test
+    public void verifySetsUpServerContextWithProperKeyMaterial()
+            throws Exception {
+        File keystoreFile = new File(this.getClass()
+                .getResource("/cmdChanServer.keystore").getFile());
+
+        PowerMockito.mockStatic(SSLKeystoreConfiguration.class);
+        when(SSLKeystoreConfiguration.getKeystoreFile()).thenReturn(
+                keystoreFile);
+        when(SSLKeystoreConfiguration.getKeyStorePassword()).thenReturn(
+                "testpassword");
+
+        PowerMockito.mockStatic(SSLContext.class);
+        SSLContext context = PowerMockito.mock(SSLContext.class);
+        when(SSLContext.getInstance("TLS")).thenReturn(context);
+        KeyManagerFactory factory = PowerMockito.mock(KeyManagerFactory.class);
+        PowerMockito.mockStatic(KeyManagerFactory.class);
+        when(KeyManagerFactory.getInstance("SunX509")).thenReturn(factory);
+
+        ArgumentCaptor<KeyStore> keystoreArgCaptor = ArgumentCaptor
+                .forClass(KeyStore.class);
+        ArgumentCaptor<char[]> pwdCaptor = ArgumentCaptor
+                .forClass(char[].class);
+        SSLContextFactory.getServerContext();
+        verify(context).init(any(KeyManager[].class),
+                any(TrustManager[].class), any(SecureRandom.class));
+        verify(factory).init(keystoreArgCaptor.capture(), pwdCaptor.capture());
+        verify(factory).getKeyManagers();
+
+        KeyStore keystore = keystoreArgCaptor.getValue();
+        String password = new String(pwdCaptor.getValue());
+        assertNotNull(keystore);
+        assertNotNull(password);
+        assertEquals("testpassword", password);
+    }
+
+    @Test
+    public void verifySetsUpClientContextWithProperTrustManager()
+            throws Exception {
+        File keystoreFile = new File(this.getClass()
+                .getResource("/cmdChanServer.keystore").getFile());
+
+        PowerMockito.mockStatic(SSLKeystoreConfiguration.class);
+        when(SSLKeystoreConfiguration.getKeystoreFile()).thenReturn(
+                keystoreFile);
+        when(SSLKeystoreConfiguration.getKeyStorePassword()).thenReturn(
+                "testpassword");
+
+        PowerMockito.mockStatic(SSLContext.class);
+        SSLContext context = PowerMockito.mock(SSLContext.class);
+        when(SSLContext.getInstance("TLS")).thenReturn(context);
+
+        ArgumentCaptor<TrustManager[]> tmsCaptor = ArgumentCaptor
+                .forClass(TrustManager[].class);
+        SSLContextFactory.getClientContext();
+        verify(context).init(any(KeyManager[].class), tmsCaptor.capture(),
+                any(SecureRandom.class));
+        TrustManager[] tms = tmsCaptor.getValue();
+        assertEquals(1, tms.length);
+        assertEquals(tms[0].getClass().getName(),
+                "com.redhat.thermostat.common.internal.CustomX509TrustManager");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/test/java/com/redhat/thermostat/common/ssl/SSLKeystoreConfigurationTest.java	Tue Dec 18 12:32:36 2012 +0100
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.ssl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration;
+
+public class SSLKeystoreConfigurationTest {
+
+    @Test
+    public void canGetKeystoreFileFromProps() throws Exception {
+        File clientProps = new File(this.getClass().getResource("/client.properties").getFile());
+        SSLKeystoreConfiguration.initClientProperties(clientProps);
+        String keystorePath = "/path/to/thermostat.keystore";
+        String keystorePwd = "some password";
+        assertEquals(keystorePath, SSLKeystoreConfiguration.getKeystoreFile().getAbsolutePath());
+        assertEquals(keystorePwd, SSLKeystoreConfiguration.getKeyStorePassword());
+    }
+    
+    @Test
+    public void notExistingPropertiesFileReturnsNull() throws Exception {
+        File clientProps = new File("i/am/not/there/file.txt");
+        SSLKeystoreConfiguration.initClientProperties(clientProps);
+        assertTrue(SSLKeystoreConfiguration.getKeystoreFile() == null);
+        assertEquals("", SSLKeystoreConfiguration.getKeyStorePassword());
+    }
+    
+    @Test
+    public void canGetSSLEnabledConfig() {
+        File clientProps = new File(this.getClass().getResource("/client.properties").getFile());
+        SSLKeystoreConfiguration.initClientProperties(clientProps);
+        assertTrue(SSLKeystoreConfiguration.shouldSSLEnableCmdChannel());
+        clientProps = new File(this.getClass().getResource("/ssl.properties").getFile());
+        SSLKeystoreConfiguration.initClientProperties(clientProps);
+        assertFalse(SSLKeystoreConfiguration.shouldSSLEnableCmdChannel());
+    }
+}
--- a/common/core/src/test/resources/client.properties	Tue Dec 18 16:39:38 2012 -0500
+++ b/common/core/src/test/resources/client.properties	Tue Dec 18 12:32:36 2012 +0100
@@ -1,2 +1,4 @@
+# Random comment
 KEYSTORE_FILE=/path/to/thermostat.keystore
-KEYSTORE_PASSWORD=some password
\ No newline at end of file
+KEYSTORE_PASSWORD=some password
+COMMAND_CHANNEL_USE_SSL=true
\ No newline at end of file
Binary file common/core/src/test/resources/cmdChanServer.keystore has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/test/resources/ssl.properties	Tue Dec 18 12:32:36 2012 +0100
@@ -0,0 +1,1 @@
+COMMAND_CHANNEL_USE_SSL=somethingNotABoolean
\ No newline at end of file
--- a/distribution/config/ssl.properties	Tue Dec 18 16:39:38 2012 -0500
+++ b/distribution/config/ssl.properties	Tue Dec 18 12:32:36 2012 +0100
@@ -9,4 +9,10 @@
 
 # The password for the keystore file. If none is provided the empty password
 # is assumed. Only used if KEYSTORE_FILE was specified.
-#KEYSTORE_PASSWORD=nopassword
\ No newline at end of file
+#KEYSTORE_PASSWORD=nopassword
+
+# Uncomment the following line if you would like to enable SSL for command
+# channel communication. Note that if this is set to true, both of the above
+# configs are required on the agent host, since it will use the key material
+# in the keystore file for SSL handshakes.
+#COMMAND_CHANNEL_USE_SSL=true
\ No newline at end of file