You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by gt...@apache.org on 2011/02/25 16:25:21 UTC

svn commit: r1074577 - in /activemq/trunk/activemq-core/src: main/java/org/apache/activemq/ActiveMQSslConnectionFactory.java test/java/org/apache/activemq/ActiveMQSslConnectionFactoryTest.java

Author: gtully
Date: Fri Feb 25 15:25:21 2011
New Revision: 1074577

URL: http://svn.apache.org/viewvc?rev=1074577&view=rev
Log:
https://issues.apache.org/jira/browse/AMQ-3192 - Add setTrustStore() and setKeyStore() methods to ActiveMQSslConnectionFactory class

Added:
    activemq/trunk/activemq-core/src/test/java/org/apache/activemq/ActiveMQSslConnectionFactoryTest.java   (with props)
Modified:
    activemq/trunk/activemq-core/src/main/java/org/apache/activemq/ActiveMQSslConnectionFactory.java

Modified: activemq/trunk/activemq-core/src/main/java/org/apache/activemq/ActiveMQSslConnectionFactory.java
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-core/src/main/java/org/apache/activemq/ActiveMQSslConnectionFactory.java?rev=1074577&r1=1074576&r2=1074577&view=diff
==============================================================================
--- activemq/trunk/activemq-core/src/main/java/org/apache/activemq/ActiveMQSslConnectionFactory.java (original)
+++ activemq/trunk/activemq-core/src/main/java/org/apache/activemq/ActiveMQSslConnectionFactory.java Fri Feb 25 15:25:21 2011
@@ -17,15 +17,30 @@
 
 package org.apache.activemq;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
 import java.security.SecureRandom;
 import javax.jms.JMSException;
 import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
 
+import org.apache.activemq.broker.BrokerService;
 import org.apache.activemq.broker.SslContext;
 import org.apache.activemq.transport.Transport;
 import org.apache.activemq.transport.tcp.SslTransportFactory;
 import org.apache.activemq.util.JMSExceptionSupport;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 
 /**
  * An ActiveMQConnectionFactory that allows access to the key and trust managers
@@ -34,13 +49,38 @@ import org.apache.activemq.util.JMSExcep
  * code. In fact, if the URI passed to this class does not have an "ssl" scheme,
  * this class will pass all work on to its superclass.
  * 
+ * There are two alternative approaches you can use to provide X.509 certificates
+ * for the SSL connections:
+ * 
+ * Call <code>setTrustStore</code>, <code>setTrustStorePassword</code>, <code>setKeyStore</code>,
+ * and <code>setKeyStorePassword</code>.
+ * 
+ * Call <code>setKeyAndTrustManagers</code>.
+ * 
  * @author sepandm@gmail.com
  */
 public class ActiveMQSslConnectionFactory extends ActiveMQConnectionFactory {
+    private static final Log LOG = LogFactory.getLog(ActiveMQSslConnectionFactory.class);
     // The key and trust managers used to initialize the used SSLContext.
     protected KeyManager[] keyManager;
     protected TrustManager[] trustManager;
     protected SecureRandom secureRandom;
+    protected String trustStore;
+    protected String trustStorePassword;
+    protected String keyStore;
+    protected String keyStorePassword;
+
+    public ActiveMQSslConnectionFactory() {
+        super();
+    }
+
+    public ActiveMQSslConnectionFactory(String brokerURL) {
+        super(brokerURL);
+    }
+
+    public ActiveMQSslConnectionFactory(URI brokerURL) {
+        super(brokerURL);
+    }
 
     /**
      * Sets the key and trust managers used when creating SSL connections.
@@ -70,6 +110,11 @@ public class ActiveMQSslConnectionFactor
         }
 
         try {
+            if (keyManager == null || trustManager == null) {
+                trustManager = createTrustManager();
+                keyManager = createKeyManager();
+                // secureRandom can be left as null
+            }
             SslTransportFactory sslFactory = new SslTransportFactory();
             SslContext ctx = new SslContext(keyManager, trustManager, secureRandom);
             SslContext.setCurrentSslContext(ctx);
@@ -78,4 +123,134 @@ public class ActiveMQSslConnectionFactor
             throw JMSExceptionSupport.create("Could not create Transport. Reason: " + e, e);
         }
     }
+
+    protected TrustManager[] createTrustManager() throws Exception {
+        TrustManager[] trustStoreManagers = null;
+        KeyStore trustedCertStore = KeyStore.getInstance("jks");
+        
+        InputStream tsStream = getUrlOrResourceAsStream(trustStore);
+        
+        trustedCertStore.load(tsStream, trustStorePassword.toCharArray());
+        TrustManagerFactory tmf  = 
+            TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+  
+        tmf.init(trustedCertStore);
+        trustStoreManagers = tmf.getTrustManagers();
+        return trustStoreManagers; 
+    }
+
+    protected KeyManager[] createKeyManager() throws Exception {
+        KeyManagerFactory kmf = 
+            KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());  
+        KeyStore ks = KeyStore.getInstance("jks");
+        KeyManager[] keystoreManagers = null;
+        
+        byte[] sslCert = loadClientCredential(keyStore);
+        
+       
+        if (sslCert != null && sslCert.length > 0) {
+            ByteArrayInputStream bin = new ByteArrayInputStream(sslCert);
+            ks.load(bin, keyStorePassword.toCharArray());
+            kmf.init(ks, keyStorePassword.toCharArray());
+            keystoreManagers = kmf.getKeyManagers();
+        }
+        return keystoreManagers;          
+    }
+
+    protected byte[] loadClientCredential(String fileName) throws IOException {
+        if (fileName == null) {
+            return null;
+        }
+        InputStream in = getUrlOrResourceAsStream(fileName);
+        //FileInputStream in = new FileInputStream(fileName);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] buf = new byte[512];
+        int i = in.read(buf);
+        while (i  > 0) {
+            out.write(buf, 0, i);
+            i = in.read(buf);
+        }
+        in.close();
+        return out.toByteArray();
+    }
+    
+    protected InputStream getUrlOrResourceAsStream(String urlOrResource) throws IOException {
+    	InputStream ins = null;
+    	try {
+    		URL url = new URL(urlOrResource);
+    		ins = url.openStream();
+    	}
+    	catch (MalformedURLException ignore) {
+    		ins = null;
+    	}
+    	
+    	// Alternatively, treat as classpath resource
+    	if (ins == null) {
+        	ins = getClass().getClassLoader().getResourceAsStream(urlOrResource);
+    	}
+    	
+    	if (ins == null) {
+            throw new java.io.IOException("Could not load resource: " + urlOrResource);
+    	}
+    	
+    	return ins;
+    }
+    
+    public String getTrustStore() {
+        return trustStore;
+    }
+    
+    /**
+     * The location of a keystore file (in <code>jks</code> format) containing one or more
+     * trusted certificates.
+     * 
+     * @param trustStore If specified with a scheme, treat as a URL, otherwise treat as a classpath resource.
+     */
+    public void setTrustStore(String trustStore) {
+        this.trustStore = trustStore;
+        trustManager = null;
+    }
+    
+    public String getTrustStorePassword() {
+        return trustStorePassword;
+    }
+    
+    /**
+     * The password to match the trust store specified by {@link setTrustStore}.
+     * 
+     * @param trustStorePassword The password used to unlock the keystore file.
+     */
+    public void setTrustStorePassword(String trustStorePassword) {
+        this.trustStorePassword = trustStorePassword;
+    }
+    
+    public String getKeyStore() {
+        return keyStore;
+    }
+    
+    /**
+     * The location of a keystore file (in <code>jks</code> format) containing a certificate
+     * and its private key.
+     * 
+     * @param keyStore If specified with a scheme, treat as a URL, otherwise treat as a classpath resource.
+     */
+    public void setKeyStore(String keyStore) {
+        this.keyStore = keyStore;
+        keyManager = null;
+    }
+    
+    public String getKeyStorePassword() {
+        return keyStorePassword;
+    }
+    
+    /**
+     * The password to match the key store specified by {@link setKeyStore}.
+     * 
+     * @param keyStorePassword The password, which is used both to unlock the keystore file
+     * and as the pass phrase for the private key stored in the keystore.
+     */
+    public void setKeyStorePassword(String keyStorePassword) {
+        this.keyStorePassword = keyStorePassword;
+    }
+
 }

Added: activemq/trunk/activemq-core/src/test/java/org/apache/activemq/ActiveMQSslConnectionFactoryTest.java
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-core/src/test/java/org/apache/activemq/ActiveMQSslConnectionFactoryTest.java?rev=1074577&view=auto
==============================================================================
--- activemq/trunk/activemq-core/src/test/java/org/apache/activemq/ActiveMQSslConnectionFactoryTest.java (added)
+++ activemq/trunk/activemq-core/src/test/java/org/apache/activemq/ActiveMQSslConnectionFactoryTest.java Fri Feb 25 15:25:21 2011
@@ -0,0 +1,220 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.KeyStore;
+
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.Session;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.activemq.broker.BrokerRegistry;
+import org.apache.activemq.broker.BrokerService;
+import org.apache.activemq.broker.SslBrokerService;
+import org.apache.activemq.broker.TransportConnector;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class ActiveMQSslConnectionFactoryTest extends CombinationTestSupport {
+    private static final Log LOG = LogFactory.getLog(ActiveMQSslConnectionFactoryTest.class);
+
+    public static final String KEYSTORE_TYPE = "jks";
+    public static final String PASSWORD = "password";
+    public static final String SERVER_KEYSTORE = "src/test/resources/server.keystore";
+    public static final String TRUST_KEYSTORE = "src/test/resources/client.keystore";
+
+    private TransportConnector connector;
+    private ActiveMQConnection connection;
+    private BrokerService broker;
+
+    protected void tearDown() throws Exception {
+        // Try our best to close any previously opend connection.
+        try {
+            connection.close();
+        } catch (Throwable ignore) {
+        }
+        // Try our best to stop any previously started broker.
+        try {
+            broker.stop();
+        } catch (Throwable ignore) {
+        }
+    }
+
+    public void testCreateTcpConnectionUsingKnownPort() throws Exception {
+        // Control case: check that the factory can create an ordinary (non-ssl) connection.
+        broker = createBroker("tcp://localhost:61610?wireFormat.tcpNoDelayEnabled=true");
+
+        // This should create the connection.
+        ActiveMQSslConnectionFactory cf = new ActiveMQSslConnectionFactory("tcp://localhost:61610?wireFormat.tcpNoDelayEnabled=true");
+        connection = (ActiveMQConnection)cf.createConnection();
+        assertNotNull(connection);
+
+	brokerStop();
+    }
+
+    public void testCreateSslConnection() throws Exception {
+        // Create SSL/TLS connection with trusted cert from truststore.
+    	String sslUri = "ssl://localhost:61611";
+        broker = createSslBroker(sslUri);
+        assertNotNull(broker);
+
+        // This should create the connection.
+        ActiveMQSslConnectionFactory cf = new ActiveMQSslConnectionFactory(sslUri);
+        cf.setTrustStore("server.keystore");
+        cf.setTrustStorePassword("password");
+        connection = (ActiveMQConnection)cf.createConnection();
+        LOG.info("Created client connection");
+        assertNotNull(connection);
+
+        brokerStop();
+    }
+
+    public void testNegativeCreateSslConnectionWithWrongPassword() throws Exception {
+        // Create SSL/TLS connection with trusted cert from truststore.
+    	String sslUri = "ssl://localhost:61611";
+        broker = createSslBroker(sslUri);
+        assertNotNull(broker);
+
+        // This should FAIL to connect, due to wrong password.
+        ActiveMQSslConnectionFactory cf = new ActiveMQSslConnectionFactory(sslUri);
+        cf.setTrustStore("server.keystore");
+        cf.setTrustStorePassword("wrongPassword");
+        try {
+            connection = (ActiveMQConnection)cf.createConnection();
+        }
+        catch (javax.jms.JMSException ignore) {
+        	// Expected exception
+        	LOG.info("Expected java.io.Exception [" + ignore + "]");
+        }
+        assertNull(connection);
+
+        brokerStop();
+    }
+
+    public void testNegativeCreateSslConnectionWithWrongCert() throws Exception {
+        // Create SSL/TLS connection with trusted cert from truststore.
+    	String sslUri = "ssl://localhost:61611";
+        broker = createSslBroker(sslUri);
+        assertNotNull(broker);
+
+        // This should FAIL to connect, due to wrong password.
+        ActiveMQSslConnectionFactory cf = new ActiveMQSslConnectionFactory(sslUri);
+        cf.setTrustStore("dummy.keystore");
+        cf.setTrustStorePassword("password");
+        try {
+            connection = (ActiveMQConnection)cf.createConnection();
+        }
+        catch (javax.jms.JMSException ignore) {
+        	// Expected exception
+        	LOG.info("Expected SSLHandshakeException [" + ignore + "]");
+        }
+        assertNull(connection);
+
+        brokerStop();
+    }
+
+    protected BrokerService createBroker(String uri) throws Exception {
+        // Start up a broker with a tcp connector.
+        BrokerService service = new BrokerService();
+        service.setPersistent(false);
+        connector = service.addConnector(uri);
+        service.start();
+
+        return service;
+    }
+
+    protected BrokerService createSslBroker(String uri) throws Exception {
+        
+        // http://java.sun.com/javase/javaseforbusiness/docs/TLSReadme.html
+        // work around: javax.net.ssl.SSLHandshakeException: renegotiation is not allowed
+        //System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
+        
+        SslBrokerService service = new SslBrokerService();
+        service.setPersistent(false);
+        
+        KeyManager[] km = getKeyManager();
+        TrustManager[] tm = getTrustManager();
+        connector = service.addSslConnector(uri, km, tm, null);
+        service.start();
+        
+        return service;
+    }
+
+    protected void brokerStop() throws Exception {
+        broker.stop();
+    }
+
+    public static TrustManager[] getTrustManager() throws Exception {
+        TrustManager[] trustStoreManagers = null;
+        KeyStore trustedCertStore = KeyStore.getInstance(ActiveMQSslConnectionFactoryTest.KEYSTORE_TYPE);
+        
+        trustedCertStore.load(new FileInputStream(ActiveMQSslConnectionFactoryTest.TRUST_KEYSTORE), null);
+        TrustManagerFactory tmf  = 
+            TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+  
+        tmf.init(trustedCertStore);
+        trustStoreManagers = tmf.getTrustManagers();
+        return trustStoreManagers; 
+    }
+
+    public static KeyManager[] getKeyManager() throws Exception {
+        KeyManagerFactory kmf = 
+            KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());  
+        KeyStore ks = KeyStore.getInstance(ActiveMQSslConnectionFactoryTest.KEYSTORE_TYPE);
+        KeyManager[] keystoreManagers = null;
+        
+        byte[] sslCert = loadClientCredential(ActiveMQSslConnectionFactoryTest.SERVER_KEYSTORE);
+        
+       
+        if (sslCert != null && sslCert.length > 0) {
+            ByteArrayInputStream bin = new ByteArrayInputStream(sslCert);
+            ks.load(bin, ActiveMQSslConnectionFactoryTest.PASSWORD.toCharArray());
+            kmf.init(ks, ActiveMQSslConnectionFactoryTest.PASSWORD.toCharArray());
+            keystoreManagers = kmf.getKeyManagers();
+        }
+        return keystoreManagers;          
+    }
+
+    private static byte[] loadClientCredential(String fileName) throws IOException {
+        if (fileName == null) {
+            return null;
+        }
+        FileInputStream in = new FileInputStream(fileName);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] buf = new byte[512];
+        int i = in.read(buf);
+        while (i  > 0) {
+            out.write(buf, 0, i);
+            i = in.read(buf);
+        }
+        in.close();
+        return out.toByteArray();
+    }
+
+}

Propchange: activemq/trunk/activemq-core/src/test/java/org/apache/activemq/ActiveMQSslConnectionFactoryTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: activemq/trunk/activemq-core/src/test/java/org/apache/activemq/ActiveMQSslConnectionFactoryTest.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date