diff --git a/activemq-core/src/main/java/org/apache/activemq/ActiveMQSslConnectionFactory.java b/activemq-core/src/main/java/org/apache/activemq/ActiveMQSslConnectionFactory.java
new file mode 100644
index 0000000000..575e432397
--- /dev/null
+++ b/activemq-core/src/main/java/org/apache/activemq/ActiveMQSslConnectionFactory.java
@@ -0,0 +1,89 @@
+/**
+ *
+ * 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 org.apache.activemq.transport.Transport;
+import org.apache.activemq.transport.TransportFactory;
+import org.apache.activemq.transport.tcp.SslTransportFactory;
+import org.apache.activemq.util.JMSExceptionSupport;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.jms.JMSException;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+
+/**
+ * An ActiveMQConnectionFactory that allows access to the key and trust managers used for SslConnections.
+ *
+ * There is no reason to use this class unless SSL is being used AND the key and trust managers need to be specified
+ * from within 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.
+ *
+ * @author sepandm@gmail.com
+ *
+ */
+public class ActiveMQSslConnectionFactory extends ActiveMQConnectionFactory {
+ // The key and trust managers used to initialize the used SSLContext.
+ protected KeyManager[] keyManager = null;
+ protected TrustManager[] trustManager = null;
+ protected SecureRandom secureRandom = null;
+
+ /**
+ * Sets the key and trust managers used when creating SSL connections.
+ *
+ * @param km The KeyManagers used.
+ * @param tm The TrustManagers used.
+ * @param random The SecureRandom number used.
+ */
+ public void setKeyAndTrustManagers(KeyManager[] km, TrustManager[] tm, SecureRandom random) {
+ keyManager = km;
+ trustManager = tm;
+ secureRandom = random;
+ }
+
+ /**
+ * Overriding to make special considerations for SSL connections.
+ *
+ * If we are not using SSL, the superclass's method is called.
+ * If we are using SSL, an SslConnectionFactory is used and it is given the
+ * needed key and trust managers.
+ *
+ * @author sepandm@gmail.com
+ */
+ protected Transport createTransport() throws JMSException {
+ // If the given URI is non-ssl, let superclass handle it.
+ if (!brokerURL.getScheme().equals("ssl")) {
+ return super.createTransport();
+ }
+
+ try {
+ SslTransportFactory sslFactory = new SslTransportFactory();
+ sslFactory.setKeyAndTrustManagers(keyManager, trustManager, secureRandom);
+ return sslFactory.doConnect(brokerURL);
+ } catch (Exception e) {
+ throw JMSExceptionSupport.create("Could not create Transport. Reason: " + e, e);
+ }
+ }
+}
diff --git a/activemq-core/src/main/java/org/apache/activemq/broker/SslBrokerService.java b/activemq-core/src/main/java/org/apache/activemq/broker/SslBrokerService.java
new file mode 100644
index 0000000000..080d7624b6
--- /dev/null
+++ b/activemq-core/src/main/java/org/apache/activemq/broker/SslBrokerService.java
@@ -0,0 +1,119 @@
+/**
+ *
+ * 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.broker;
+
+import org.apache.activemq.transport.TransportFactory;
+import org.apache.activemq.transport.TransportServer;
+import org.apache.activemq.transport.tcp.SslTransportFactory;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.KeyManagementException;
+import java.security.SecureRandom;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.TrustManager;
+
+/**
+ * A BrokerService that allows access to the key and trust managers used by SSL connections.
+ *
+ * There is no reason to use this class unless SSL is being used AND the key and trust managers need to be specified
+ * from within 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.
+ *
+ * @author sepandm@gmail.com (Sepand)
+ */
+public class SslBrokerService extends BrokerService {
+ /**
+ * Adds a new transport connector for the given bind address.
+ *
+ * If the transport created uses SSL, it will also use the key and trust
+ * managers provided. Otherwise, this is the same as calling
+ * addConnector.
+ *
+ * @param bindAddress The address to bind to.
+ * @param km The KeyManager to be used.
+ * @param tm The trustmanager to be used.
+ * @param random The source of randomness for the generator.
+ * @return the newly connected and added transport connector.
+ * @throws Exception
+ */
+
+ public TransportConnector addSslConnector(
+ String bindAddress,
+ KeyManager[] km,
+ TrustManager[] tm,
+ SecureRandom random) throws Exception {
+ return addSslConnector( new URI(bindAddress), km, tm, random );
+ }
+
+ /**
+ * Adds a new transport connector for the given bind address.
+ *
+ * If the transport created uses SSL, it will also use the key and trust
+ * managers provided. Otherwise, this is the same as calling
+ * addConnector.
+ *
+ * @param bindAddress The URI to bind to.
+ * @param km The KeyManager to be used.
+ * @param tm The trustmanager to be used.
+ * @param random The source of randomness for the generator.
+ * @return the newly created and added transport connector.
+ * @throws Exception
+ */
+ public TransportConnector addSslConnector(
+ URI bindAddress,
+ KeyManager[] km,
+ TrustManager[] tm,
+ SecureRandom random) throws Exception {
+ return addConnector(createSslTransportServer(bindAddress, km, tm, random));
+ }
+
+ /**
+ * Creates a TransportServer that uses the given key and trust managers.
+ *
+ * The last three parameters will be eventually passed to SSLContext.init.
+ *
+ * @param brokerURI The URI to bind to.
+ * @param km The KeyManager to be used.
+ * @param tm The trustmanager to be used.
+ * @param random The source of randomness for the generator.
+ * @return A new TransportServer that uses the given managers.
+ * @throws IOException If cannot handle URI.
+ * @throws KeyManagementException Passed on from SSL.
+ */
+ protected TransportServer createSslTransportServer(
+ URI brokerURI,
+ KeyManager[] km,
+ TrustManager[] tm,
+ SecureRandom random) throws IOException, KeyManagementException {
+
+ if (brokerURI.getScheme().equals("ssl")) {
+ // If given an SSL URI, use an SSL TransportFactory and configure
+ // it to use the given key and trust managers.
+ SslTransportFactory transportFactory = new SslTransportFactory();
+ transportFactory.setKeyAndTrustManagers(km, tm, random);
+
+ return transportFactory.doBind(getBrokerName(),brokerURI);
+ } else {
+ // Else, business as usual.
+ return TransportFactory.bind(getBrokerName(), brokerURI);
+ }
+ }
+}
diff --git a/activemq-core/src/main/java/org/apache/activemq/security/JaasAuthenticationPlugin.java.orig b/activemq-core/src/main/java/org/apache/activemq/security/JaasAuthenticationPlugin.java.orig
new file mode 100644
index 0000000000..106700b4aa
--- /dev/null
+++ b/activemq-core/src/main/java/org/apache/activemq/security/JaasAuthenticationPlugin.java.orig
@@ -0,0 +1,88 @@
+/**
+ *
+ * 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.security;
+
+import org.apache.activemq.broker.Broker;
+import org.apache.activemq.broker.BrokerPlugin;
+
+import java.net.URL;
+
+/**
+ * Adds a JAAS based authentication security plugin
+ *
+ * @org.apache.xbean.XBean description="Provides a JAAS based authentication plugin"
+ *
+ * @version $Revision: 426366 $
+ */
+public class JaasAuthenticationPlugin implements BrokerPlugin {
+
+ private String configuration = "activemq-domain";
+ private boolean discoverLoginConfig = true;
+
+ public Broker installPlugin(Broker broker) {
+ initialiseJaas();
+ return new JaasAuthenticationBroker(broker, configuration);
+ }
+
+
+ // Properties
+ // -------------------------------------------------------------------------
+ public String getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * Sets the JAAS configuration domain name used
+ */
+ public void setConfiguration(String jaasConfiguration) {
+ this.configuration = jaasConfiguration;
+ }
+
+
+ public boolean isDiscoverLoginConfig() {
+ return discoverLoginConfig;
+ }
+
+ /**
+ * Enables or disables the auto-discovery of the login.config file for JAAS to initialize itself.
+ * This flag is enabled by default such that if the java.security.auth.login.config system property
+ * is not defined then it is set to the location of the login.config file on the classpath.
+ */
+ public void setDiscoverLoginConfig(boolean discoverLoginConfig) {
+ this.discoverLoginConfig = discoverLoginConfig;
+ }
+
+ // Implementation methods
+ // -------------------------------------------------------------------------
+ protected void initialiseJaas() {
+ if (discoverLoginConfig) {
+ String path = System.getProperty("java.security.auth.login.config");
+ if (path == null) {
+ //URL resource = Thread.currentThread().getContextClassLoader().getResource("login.config");
+ URL resource = null;
+ if (resource == null) {
+ resource = getClass().getClassLoader().getResource("login.config");
+ }
+ if (resource != null) {
+ path = resource.getFile();
+ System.setProperty("java.security.auth.login.config", path);
+ }
+ }
+ }
+ }
+}
diff --git a/activemq-core/src/main/java/org/apache/activemq/security/JaasCertificateAuthenticationBroker.java b/activemq-core/src/main/java/org/apache/activemq/security/JaasCertificateAuthenticationBroker.java
new file mode 100644
index 0000000000..84e3e337d5
--- /dev/null
+++ b/activemq-core/src/main/java/org/apache/activemq/security/JaasCertificateAuthenticationBroker.java
@@ -0,0 +1,120 @@
+/**
+ *
+ * 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.security;
+
+import org.apache.activemq.broker.Broker;
+import org.apache.activemq.broker.BrokerFilter;
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.jaas.JaasCertificateCallbackHandler;
+import org.apache.activemq.jaas.UserPrincipal;
+import org.apache.activemq.security.JaasAuthenticationBroker.JaasSecurityContext;
+
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.Iterator;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+
+/**
+ * A JAAS Authentication Broker that uses SSL Certificates.
+ *
+ * This class will provide the JAAS framework with a JaasCertificateCallbackHandler that will grant JAAS access to
+ * incoming connections' SSL certificate chains.
+ * NOTE: There is a chance that the incoming connection does not have a valid certificate (has null).
+ *
+ * @author sepandm@gmail.com (Sepand)
+ */
+public class JaasCertificateAuthenticationBroker extends BrokerFilter {
+ private final String jaasConfiguration;
+
+ /**
+ * Simple constructor. Leaves everything to superclass.
+ *
+ * @param next The Broker that does the actual work for this Filter.
+ * @param jassConfiguration The JAAS domain configuration name (refere to JAAS documentation).
+ */
+ public JaasCertificateAuthenticationBroker(Broker next, String jaasConfiguration) {
+ super(next);
+
+ this.jaasConfiguration = jaasConfiguration;
+ }
+
+ /**
+ * Overridden to allow for authentication based on client certificates.
+ *
+ * Connections being added will be authenticated based on their certificate chain and the JAAS module specified
+ * through the JAAS framework.
+ * NOTE: The security context's username will be set to the first UserPrincipal created by the login module.
+ *
+ * @param context The context for the incoming Connection.
+ * @param info The ConnectionInfo Command representing the incoming connection.
+ */
+ public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
+
+ if (context.getSecurityContext() == null) {
+ if (!( info.getTransportContext() instanceof X509Certificate[] )) {
+ throw new SecurityException("Unable to authenticate transport without SSL certificate.");
+ }
+
+ // Set the TCCL since it seems JAAS needs it to find the login module classes.
+ ClassLoader original = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(JaasAuthenticationBroker.class.getClassLoader());
+ try {
+ // Do the login.
+ try {
+ CallbackHandler callback =
+ new JaasCertificateCallbackHandler((X509Certificate[])info.getTransportContext());
+ LoginContext lc = new LoginContext(jaasConfiguration, callback);
+ lc.login();
+ Subject subject = lc.getSubject();
+
+ String dnName = "";
+
+ for (Iterator iter = subject.getPrincipals().iterator(); iter.hasNext(); ) {
+ Principal nextPrincipal = (Principal)iter.next();
+ if (nextPrincipal instanceof UserPrincipal) {
+ dnName = ((UserPrincipal)nextPrincipal).getName();
+ break;
+ }
+ }
+
+ SecurityContext s = new JaasSecurityContext(dnName, subject);
+ context.setSecurityContext(s);
+ } catch (Exception e) {
+ throw new SecurityException("User name or password is invalid.", e);
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(original);
+ }
+ }
+ super.addConnection(context, info);
+ }
+
+ /**
+ * Overriding removeConnection to make sure the security context is cleaned.
+ */
+ public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
+ super.removeConnection(context, info, error);
+
+ context.setSecurityContext(null);
+ }
+}
diff --git a/activemq-core/src/main/java/org/apache/activemq/security/JaasCertificateAuthenticationPlugin.java b/activemq-core/src/main/java/org/apache/activemq/security/JaasCertificateAuthenticationPlugin.java
new file mode 100644
index 0000000000..93c66566c3
--- /dev/null
+++ b/activemq-core/src/main/java/org/apache/activemq/security/JaasCertificateAuthenticationPlugin.java
@@ -0,0 +1,36 @@
+/**
+ *
+ * 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.security;
+
+import org.apache.activemq.broker.Broker;
+
+/**
+ * A JAAS based SSL certificate authentication plugin.
+ *
+ * @org.apache.xbean.XBean description="Provides a JAAS based SSL certificate authentication plugin"
+ *
+ * @author sepandm@gmail.com (Sepand)
+ *
+ */
+public class JaasCertificateAuthenticationPlugin extends JaasAuthenticationPlugin {
+ public Broker installPlugin(Broker broker) {
+ initialiseJaas();
+ return new JaasCertificateAuthenticationBroker(broker, configuration);
+ }
+}
diff --git a/activemq-core/src/main/java/org/apache/activemq/transport/tcp/SslTransport.java b/activemq-core/src/main/java/org/apache/activemq/transport/tcp/SslTransport.java
new file mode 100644
index 0000000000..306a1dfc7c
--- /dev/null
+++ b/activemq-core/src/main/java/org/apache/activemq/transport/tcp/SslTransport.java
@@ -0,0 +1,110 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import org.apache.activemq.wireformat.WireFormat;
+import org.apache.activemq.command.Command;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.util.IntrospectionSupport;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLPeerUnverifiedException;
+
+/**
+ * A Transport class that uses SSL and client-side certificate authentication.
+ *
+ * Client-side certificate authentication must be enabled through the constructor.
+ * By default, this class will have the same client authentication behavior as the socket it is passed.
+ * This class will set ConnectionInfo's transportContext to the SSL certificates of the client.
+ * NOTE: Accessor method for needClientAuth was not provided on purpose. This is because needClientAuth's value must be
+ * set before the socket is connected. Otherwise, unexpected situations may occur.
+ *
+ */
+class SslTransport extends TcpTransport {
+ /**
+ * Connect to a remote node such as a Broker.
+ *
+ * @param wireFormat The WireFormat to be used.
+ * @param socketFactory The socket factory to be used. Forcing SSLSockets
+ * for obvious reasons.
+ * @param remoteLocation The remote location.
+ * @param localLocation The local location.
+ * @param needClientAuth If set to true, the underlying socket will need
+ * client certificate authentication.
+ * @throws UnknownHostException If TcpTransport throws.
+ * @throws IOException If TcpTransport throws.
+ */
+ public SslTransport(WireFormat wireFormat, SSLSocketFactory socketFactory, URI remoteLocation, URI localLocation, boolean needClientAuth) throws IOException {
+ super(wireFormat, socketFactory, remoteLocation, localLocation);
+ ((SSLSocket)this.socket).setNeedClientAuth(needClientAuth);
+ }
+
+ /**
+ * Initialize from a ServerSocket.
+ *
+ * No access to needClientAuth is given since it is already set within the
+ * provided socket.
+ *
+ * @param wireFormat The WireFormat to be used.
+ * @param socket The Socket to be used. Forcing SSL.
+ * @throws IOException If TcpTransport throws.
+ */
+ public SslTransport(WireFormat wireFormat, SSLSocket socket) throws IOException {
+ super(wireFormat, socket);
+ }
+
+ /**
+ * Overriding in order to add the client's certificates to ConnectionInfo Commmands.
+ *
+ * @param command The Command coming in.
+ */
+ public void doConsume(Command command) {
+ // The instanceof can be avoided, but that would require modifying the
+ // Command clas tree and that would require too much effort right
+ // now.
+ if ( command instanceof ConnectionInfo ) {
+ ConnectionInfo connectionInfo = (ConnectionInfo)command;
+
+ SSLSocket sslSocket = (SSLSocket)this.socket;
+
+ SSLSession sslSession = sslSocket.getSession();
+
+ X509Certificate[] clientCertChain;
+ try {
+ clientCertChain =
+ (X509Certificate[]) sslSession.getPeerCertificates();
+ } catch(SSLPeerUnverifiedException e) {
+ clientCertChain = null;
+ }
+
+ connectionInfo.setTransportContext(clientCertChain);
+ }
+
+ super.doConsume(command);
+ }
+}
+
diff --git a/activemq-core/src/main/java/org/apache/activemq/transport/tcp/SslTransportServer.java b/activemq-core/src/main/java/org/apache/activemq/transport/tcp/SslTransportServer.java
new file mode 100644
index 0000000000..167f50f59a
--- /dev/null
+++ b/activemq-core/src/main/java/org/apache/activemq/transport/tcp/SslTransportServer.java
@@ -0,0 +1,127 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import org.apache.activemq.wireformat.WireFormat;
+import org.apache.activemq.transport.Transport;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * An SSL TransportServer.
+ *
+ * Allows for client certificate authentication (refer to setNeedClientAuth for
+ * details).
+ * NOTE: Client certificate authentication is disabled by default.
+ *
+ */
+public class SslTransportServer extends TcpTransportServer {
+
+ // Specifies if sockets created from this server should needClientAuth.
+ private boolean needClientAuth = false;
+
+ // Specifies if sockets created from this server should wantClientAuth.
+ private boolean wantClientAuth = false;
+
+
+ /**
+ * Constructor.
+ *
+ * @param transportFactory The factory used to create transports when connections arrive.
+ * @param location The location of the broker to bind to.
+ * @param serverSocketFactory The factory used to create this server.
+ * @param needClientAuth States if this server should needClientAuth.
+ * @throws IOException passed up from TcpTransportFactory.
+ * @throws URISyntaxException passed up from TcpTransportFactory.
+ */
+ public SslTransportServer(
+ SslTransportFactory transportFactory,
+ URI location,
+ SSLServerSocketFactory serverSocketFactory) throws IOException, URISyntaxException {
+ super(transportFactory, location, serverSocketFactory);
+ }
+
+ /**
+ * Setter for needClientAuth.
+ *
+ * When set to true, needClientAuth will set SSLSockets' needClientAuth to true forcing clients to provide
+ * client certificates.
+ */
+ public void setNeedClientAuth(boolean needAuth) {
+ this.needClientAuth = needAuth;
+ }
+
+ /**
+ * Getter for needClientAuth.
+ */
+ public boolean getNeedClientAuth() {
+ return this.needClientAuth;
+ }
+
+ /**
+ * Getter for wantClientAuth.
+ */
+ public boolean getWantClientAuth() {
+ return this.wantClientAuth;
+ }
+
+ /**
+ * Setter for wantClientAuth.
+ *
+ * When set to true, wantClientAuth will set SSLSockets' wantClientAuth to true forcing clients to provide
+ * client certificates.
+ */
+ public void setWantClientAuth(boolean wantAuth) {
+ this.wantClientAuth = wantAuth;
+ }
+
+ /**
+ * Binds this socket to the previously specified URI.
+ *
+ * Overridden to allow for proper handling of needClientAuth.
+ *
+ * @throws IOException passed up from TcpTransportServer.
+ */
+ public void bind() throws IOException {
+ super.bind();
+ ((SSLServerSocket)this.serverSocket).setWantClientAuth(wantClientAuth);
+ ((SSLServerSocket)this.serverSocket).setNeedClientAuth(needClientAuth);
+ }
+
+ /**
+ * Used to create Transports for this server.
+ *
+ * Overridden to allow the use of SslTransports (instead of TcpTransports).
+ *
+ * @param socket The incoming socket that will be wrapped into the new Transport.
+ * @param format The WireFormat being used.
+ * @return The newly return (SSL) Transport.
+ * @throws IOException
+ */
+ protected Transport createTransport(Socket socket, WireFormat format) throws IOException {
+ return new SslTransport(format, (SSLSocket)socket);
+ }
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/broker/StubBroker.java b/activemq-core/src/test/java/org/apache/activemq/broker/StubBroker.java
new file mode 100644
index 0000000000..e48d721adb
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/broker/StubBroker.java
@@ -0,0 +1,226 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.broker;
+
+import org.apache.activemq.broker.region.Destination;
+import org.apache.activemq.broker.region.Subscription;
+import org.apache.activemq.command.ActiveMQDestination;
+import org.apache.activemq.command.BrokerId;
+import org.apache.activemq.command.BrokerInfo;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.command.ConsumerInfo;
+import org.apache.activemq.command.DestinationInfo;
+import org.apache.activemq.command.Message;
+import org.apache.activemq.command.MessageAck;
+import org.apache.activemq.command.MessageDispatch;
+import org.apache.activemq.command.MessageDispatchNotification;
+import org.apache.activemq.command.MessagePull;
+import org.apache.activemq.command.ProducerInfo;
+import org.apache.activemq.command.RemoveSubscriptionInfo;
+import org.apache.activemq.command.Response;
+import org.apache.activemq.command.SessionInfo;
+import org.apache.activemq.command.TransactionId;
+import org.apache.activemq.kaha.Store;
+
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+public class StubBroker implements Broker {
+ public LinkedList addConnectionData = new LinkedList();
+ public LinkedList removeConnectionData = new LinkedList();
+
+ public class AddConnectionData {
+ public final ConnectionContext connectionContext;
+ public final ConnectionInfo connectionInfo;
+
+ public AddConnectionData(ConnectionContext context, ConnectionInfo info) {
+ connectionContext = context;
+ connectionInfo = info;
+ }
+ }
+
+ public class RemoveConnectionData {
+ public final ConnectionContext connectionContext;
+ public final ConnectionInfo connectionInfo;
+ public final Throwable error;
+
+ public RemoveConnectionData(ConnectionContext context, ConnectionInfo info, Throwable error) {
+ connectionContext = context;
+ connectionInfo = info;
+ this.error = error;
+ }
+ }
+
+ public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
+ addConnectionData.add(new AddConnectionData(context, info));
+ }
+
+ public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
+ removeConnectionData.add(new RemoveConnectionData(context, info, error));
+ }
+
+
+ // --- Blank Methods, fill in as needed ---
+ public void addBroker(Connection connection, BrokerInfo info) {
+ }
+
+ public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
+ }
+
+ public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {
+ }
+
+ public void addSession(ConnectionContext context, SessionInfo info) throws Exception {
+ }
+
+ public void beginTransaction(ConnectionContext context, TransactionId xid) throws Exception {
+ }
+
+ public void commitTransaction(ConnectionContext context, TransactionId xid, boolean onePhase) throws Exception {
+ }
+
+ public void forgetTransaction(ConnectionContext context, TransactionId transactionId) throws Exception {
+ }
+
+ public Broker getAdaptor(Class type) {
+ return null;
+ }
+
+ public ConnectionContext getAdminConnectionContext() {
+ return null;
+ }
+
+ public BrokerId getBrokerId() {
+ return null;
+ }
+
+ public String getBrokerName() {
+ return null;
+ }
+
+ public Connection[] getClients() throws Exception {
+ return null;
+ }
+
+ public ActiveMQDestination[] getDestinations() throws Exception {
+ return null;
+ }
+
+ public Set getDurableDestinations() {
+ return null;
+ }
+
+ public BrokerInfo[] getPeerBrokerInfos() {
+ return null;
+ }
+
+ public TransactionId[] getPreparedTransactions(ConnectionContext context) throws Exception {
+ return null;
+ }
+
+ public boolean isFaultTolerantConfiguration() {
+ return false;
+ }
+
+ public boolean isSlaveBroker() {
+ return false;
+ }
+
+ public boolean isStopped() {
+ return false;
+ }
+
+ public int prepareTransaction(ConnectionContext context, TransactionId xid) throws Exception {
+ return 0;
+ }
+
+ public void processDispatch(MessageDispatch messageDispatch) {
+ }
+
+ public void removeBroker(Connection connection, BrokerInfo info) {
+ }
+
+ public void removeDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
+ }
+
+ public void removeProducer(ConnectionContext context, ProducerInfo info) throws Exception {
+ }
+
+ public void removeSession(ConnectionContext context, SessionInfo info) throws Exception {
+ }
+
+ public void rollbackTransaction(ConnectionContext context, TransactionId xid) throws Exception {
+ }
+
+ public void setAdminConnectionContext(ConnectionContext adminConnectionContext) {
+ }
+
+ public void acknowledge(ConnectionContext context, MessageAck ack) throws Exception {
+ }
+
+ public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
+ return null;
+ }
+
+ public Destination addDestination(ConnectionContext context, ActiveMQDestination destination) throws Exception {
+ return null;
+ }
+
+ public void gc() {
+ }
+
+ public Map getDestinationMap() {
+ return null;
+ }
+
+ public Set getDestinations(ActiveMQDestination destination) {
+ return null;
+ }
+
+ public Response messagePull(ConnectionContext context, MessagePull pull) throws Exception {
+ return null;
+ }
+
+ public void processDispatchNotification(MessageDispatchNotification messageDispatchNotification) throws Exception {
+ }
+
+ public void removeConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
+ }
+
+ public void removeDestination(ConnectionContext context, ActiveMQDestination destination, long timeout)
+ throws Exception {
+ }
+
+ public void removeSubscription(ConnectionContext context, RemoveSubscriptionInfo info) throws Exception {
+ }
+
+ public void send(ConnectionContext context, Message message) throws Exception {
+ }
+
+ public void start() throws Exception {
+ }
+
+ public void stop() throws Exception {
+ }
+
+ public Store getTempDataStore() {
+ return null;
+ }
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/security/JaasCertificateAuthenticationBrokerTest.java b/activemq-core/src/test/java/org/apache/activemq/security/JaasCertificateAuthenticationBrokerTest.java
new file mode 100644
index 0000000000..4c443f6b1c
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/security/JaasCertificateAuthenticationBrokerTest.java
@@ -0,0 +1,206 @@
+/**
+ *
+ * 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.security;
+
+import junit.framework.TestCase;
+
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.broker.StubBroker;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.jaas.GroupPrincipal;
+import org.apache.activemq.jaas.UserPrincipal;
+import org.apache.activemq.transport.tcp.StubX509Certificate;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+
+public class JaasCertificateAuthenticationBrokerTest extends TestCase {
+ StubBroker receiveBroker;
+
+ JaasCertificateAuthenticationBroker authBroker;
+
+ ConnectionContext connectionContext;
+ ConnectionInfo connectionInfo;
+
+ protected void setUp() throws Exception {
+ receiveBroker = new StubBroker();
+
+ authBroker = new JaasCertificateAuthenticationBroker(receiveBroker, "");
+
+ connectionContext = new ConnectionContext();
+ connectionInfo = new ConnectionInfo();
+
+ connectionInfo.setTransportContext(new StubX509Certificate[] {});
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ private void setConfiguration(Set userNames, Set groupNames, boolean loginShouldSucceed) {
+ HashMap configOptions = new HashMap();
+
+ String userNamesString;
+ {
+ Iterator iter = userNames.iterator();
+ userNamesString = "" + (iter.hasNext() ? (String)iter.next() : "");
+ while (iter.hasNext()) {
+ userNamesString += "," + (String)iter.next();
+ }
+ }
+
+ String groupNamesString = "";
+ {
+ Iterator iter = groupNames.iterator();
+ groupNamesString = "" + (iter.hasNext() ? (String)iter.next() : "");
+ while (iter.hasNext()) {
+ groupNamesString += "," + (String)iter.next();
+ }
+ }
+
+ configOptions.put(StubLoginModule.ALLOW_LOGIN_PROPERTY, (loginShouldSucceed ? "true" : "false"));
+ configOptions.put(StubLoginModule.USERS_PROPERTY, userNamesString);
+ configOptions.put(StubLoginModule.GROUPS_PROPERTY, groupNamesString);
+ AppConfigurationEntry configEntry = new AppConfigurationEntry(
+ "org.apache.activemq.security.StubLoginModule",
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+ configOptions);
+
+ StubJaasConfiguration jaasConfig = new StubJaasConfiguration(configEntry);
+
+ Configuration.setConfiguration(jaasConfig);
+ }
+
+ public void testAddConnectionSuccess() {
+ String dnUserName = "dnUserName";
+
+ HashSet userNames = new HashSet();
+ userNames.add(dnUserName);
+
+ HashSet groupNames = new HashSet();
+ groupNames.add("testGroup1");
+ groupNames.add("testGroup2");
+ groupNames.add("tesetGroup3");
+
+ setConfiguration(
+ userNames,
+ groupNames,
+ true);
+
+ try {
+ authBroker.addConnection(connectionContext, connectionInfo);
+ } catch (Exception e) {
+ fail("Call to addConnection failed: " + e.getMessage());
+ }
+
+ assertEquals("Number of addConnection calls to underlying Broker must match number of calls made to " +
+ "AuthenticationBroker.",
+ 1, receiveBroker.addConnectionData.size());
+
+ ConnectionContext receivedContext =
+ ((StubBroker.AddConnectionData)receiveBroker.addConnectionData.getFirst()).connectionContext;
+
+ assertEquals("The SecurityContext's userName must be set to that of the UserPrincipal.",
+ dnUserName, receivedContext.getSecurityContext().getUserName());
+
+ Set receivedPrincipals =
+ receivedContext.getSecurityContext().getPrincipals();
+
+ for (Iterator iter = receivedPrincipals.iterator(); iter.hasNext(); ) {
+ Principal currentPrincipal = (Principal)iter.next();
+
+ if (currentPrincipal instanceof UserPrincipal) {
+ if (userNames.remove(currentPrincipal.getName())) {
+ // Nothing, we did good.
+ } else {
+ // Found an unknown userName.
+ fail("Unknown UserPrincipal found");
+ }
+ } else if (currentPrincipal instanceof GroupPrincipal) {
+ if (groupNames.remove(currentPrincipal.getName())) {
+ // Nothing, we did good.
+ } else {
+ fail("Unknown GroupPrincipal found.");
+ }
+ } else {
+ fail("Unexpected Principal subclass found.");
+ }
+ }
+
+ if (!userNames.isEmpty()) {
+ fail("Some usernames were not added as UserPrincipals");
+ }
+
+ if (!groupNames.isEmpty()) {
+ fail("Some group names were not added as GroupPrincipals");
+ }
+ }
+
+ public void testAddConnectionFailure() {
+ HashSet userNames = new HashSet();
+
+ HashSet groupNames = new HashSet();
+ groupNames.add("testGroup1");
+ groupNames.add("testGroup2");
+ groupNames.add("tesetGroup3");
+
+ setConfiguration(
+ userNames,
+ groupNames,
+ false);
+
+ boolean connectFailed = false;
+ try {
+ authBroker.addConnection(connectionContext, connectionInfo);
+ } catch (SecurityException e) {
+ connectFailed = true;
+ } catch (Exception e) {
+ fail("Failed to connect for unexpected reason: " + e.getMessage());
+ }
+
+ if (!connectFailed) {
+ fail("Unauthenticated connection allowed.");
+ }
+
+ assertEquals("Unauthenticated connection allowed.",
+ true, receiveBroker.addConnectionData.isEmpty());
+ }
+
+ public void testRemoveConnection() throws Exception {
+ connectionContext.setSecurityContext(new StubSecurityContext());
+
+ authBroker.removeConnection(connectionContext, connectionInfo, new Throwable());
+
+ assertEquals("removeConnection should clear ConnectionContext.",
+ null, connectionContext.getSecurityContext());
+
+ assertEquals("Incorrect number of calls to underlying broker were made.",
+ 1, receiveBroker.removeConnectionData.size());
+ }
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/security/StubDoNothingCallbackHandler.java b/activemq-core/src/test/java/org/apache/activemq/security/StubDoNothingCallbackHandler.java
new file mode 100644
index 0000000000..d56013e946
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/security/StubDoNothingCallbackHandler.java
@@ -0,0 +1,32 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.security;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+public class StubDoNothingCallbackHandler implements CallbackHandler {
+
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ }
+
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/security/StubJaasConfiguration.java b/activemq-core/src/test/java/org/apache/activemq/security/StubJaasConfiguration.java
new file mode 100644
index 0000000000..18c3221e6d
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/security/StubJaasConfiguration.java
@@ -0,0 +1,38 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.security;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+
+public class StubJaasConfiguration extends Configuration {
+ private AppConfigurationEntry configEntry;
+
+ public StubJaasConfiguration(AppConfigurationEntry configEntry) {
+ this.configEntry = configEntry;
+ }
+
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ return new AppConfigurationEntry[] { configEntry };
+ }
+
+ public void refresh() {
+ }
+
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/security/StubLoginModule.java b/activemq-core/src/test/java/org/apache/activemq/security/StubLoginModule.java
new file mode 100644
index 0000000000..525ce99d88
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/security/StubLoginModule.java
@@ -0,0 +1,93 @@
+/**
+ *
+ * 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.security;
+
+import org.apache.activemq.jaas.GroupPrincipal;
+import org.apache.activemq.jaas.UserPrincipal;
+
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+public class StubLoginModule implements LoginModule {
+ public static final String ALLOW_LOGIN_PROPERTY = "org.apache.activemq.jaas.stubproperties.allow_login";
+ public static final String USERS_PROPERTY = "org.apache.activemq.jaas.stubproperties.users";
+ public static final String GROUPS_PROPERTY = "org.apache.activemq.jaas.stubproperties.groups";
+
+ private Subject subject = null;
+
+ private String userNames[] = null;
+ private String groupNames[] = null;
+ private boolean allowLogin = false;
+
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+ String allowLoginString = (String)(options.get(ALLOW_LOGIN_PROPERTY));
+ String usersString = (String)(options.get(USERS_PROPERTY));
+ String groupsString = (String)(options.get(GROUPS_PROPERTY));
+
+ this.subject = subject;
+
+ allowLogin = Boolean.parseBoolean(allowLoginString);
+ userNames = usersString.split(",");
+ groupNames = groupsString.split(",");
+ }
+
+ public boolean login() throws LoginException {
+ if (!allowLogin) {
+ throw new FailedLoginException("Login was not allowed (as specified in configuration).");
+ }
+
+ return true;
+ }
+
+ public boolean commit() throws LoginException {
+ if (!allowLogin) {
+ throw new FailedLoginException("Login was not allowed (as specified in configuration).");
+ }
+
+ for (int i = 0; i < userNames.length; ++i) {
+ if (userNames[i].length() > 0 ) {
+ subject.getPrincipals().add(new UserPrincipal(userNames[i]));
+ }
+ }
+
+ for (int i = 0; i < groupNames.length; ++i) {
+ if (groupNames[i].length() > 0) {
+ subject.getPrincipals().add(new GroupPrincipal(groupNames[i]));
+ }
+ }
+
+ return true;
+ }
+
+ public boolean abort() throws LoginException {
+ return true;
+ }
+
+ public boolean logout() throws LoginException {
+ subject.getPrincipals().clear();
+
+ return true;
+ }
+
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/security/StubSecurityContext.java b/activemq-core/src/test/java/org/apache/activemq/security/StubSecurityContext.java
new file mode 100644
index 0000000000..3d7f7453fb
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/security/StubSecurityContext.java
@@ -0,0 +1,31 @@
+/**
+ *
+ * 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.security;
+
+import java.util.Set;
+
+public class StubSecurityContext extends SecurityContext {
+ StubSecurityContext() {
+ super("");
+ }
+
+ public Set getPrincipals() {
+ return null;
+ }
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/transport/tcp/SslTransportFactoryTest.java b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/SslTransportFactoryTest.java
new file mode 100644
index 0000000000..7e1080b308
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/SslTransportFactoryTest.java
@@ -0,0 +1,129 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import junit.framework.TestCase;
+
+import org.apache.activemq.openwire.OpenWireFormat;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SslTransportFactoryTest extends TestCase {
+ private SslTransportFactory factory;
+
+ protected void setUp() throws Exception {
+ factory = new SslTransportFactory();
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testBindServerOptions() throws IOException {
+
+ SslTransportServer sslTransportServer = null;
+
+ for (int i = 0; i < 4; ++i) {
+ final boolean wantClientAuth = ((i & 0x1) == 1);
+ final boolean needClientAuth = ((i & 0x2) == 1);
+
+ String options = "wantClientAuth=" + (wantClientAuth ? "true" : "false") +
+ "&needClientAuth=" + (needClientAuth ? "true" : "false");
+
+ try {
+ sslTransportServer = (SslTransportServer)
+ factory.doBind("brokerId", new URI("ssl://localhost:61616?" + options));
+ } catch (Exception e) {
+ fail("Unable to bind to address: " + e.getMessage());
+ }
+
+ assertEquals("Created ServerSocket did not have correct wantClientAuth status.",
+ sslTransportServer.getWantClientAuth(), wantClientAuth);
+
+ assertEquals("Created ServerSocket did not have correct needClientAuth status.",
+ sslTransportServer.getNeedClientAuth(), needClientAuth);
+
+ try {
+ sslTransportServer.stop();
+ } catch (Exception e) {
+ fail("Unable to stop TransportServer: " + e.getMessage());
+ }
+ }
+ }
+
+ private int getMthNaryDigit(int number, int digitIdx, int numBase) {
+ return (number / ((int)Math.pow(numBase, digitIdx)) ) % numBase;
+ }
+
+ public void testCompositeConfigure() throws IOException {
+ // The 5 options being tested.
+ int optionSettings[] = new int[5];
+
+ String optionNames[] = {
+ "wantClientAuth",
+ "needClientAuth",
+ "socket.wantClientAuth",
+ "socket.needClientAuth",
+ "socket.useClientMode"
+ };
+
+ // Using a trinary interpretation of i to set all possible values of stub options for socket and transport.
+ // 2 transport options, 3 socket options, 3 settings for each option => 3^5 = 243 combos.
+ for (int i = 0; i < 243; ++i) {
+ Map options = new HashMap();
+
+ for (int j = 0; j < 5; ++j) {
+ // -1 since the option range is [-1,1], not [0,2].
+ optionSettings[j] = getMthNaryDigit(i, j, 3) - 1;
+
+ if ( optionSettings[j] != -1) {
+ options.put(optionNames[j], (optionSettings[j] == 1 ? "true" : "false"));
+ }
+ }
+
+ StubSSLSocket socketStub = new StubSSLSocket(null);
+ StubSslTransport transport = null;
+
+ try {
+ transport = new StubSslTransport(null, socketStub);
+ } catch (Exception e) {
+ fail("Unable to create StubSslTransport: " + e.getMessage());
+ }
+
+ factory.compositeConfigure(transport, new OpenWireFormat(), options);
+
+ if (socketStub.getWantClientAuthStatus() != optionSettings[2])
+ System.out.println("sheiite");
+
+ assertEquals("wantClientAuth was not properly set",
+ optionSettings[0], transport.getWantClientAuthStatus());
+ assertEquals("needClientAuth was not properly set",
+ optionSettings[1], transport.getNeedClientAuthStatus());
+ assertEquals("socket.wantClientAuth was not properly set",
+ optionSettings[2], socketStub.getWantClientAuthStatus());
+ assertEquals("socket.needClientAuth was not properly set",
+ optionSettings[3], socketStub.getNeedClientAuthStatus());
+ assertEquals("socket.useClientMode was not properly set",
+ optionSettings[4], socketStub.getUseClientModeStatus());
+ }
+ }
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/transport/tcp/SslTransportServerTest.java b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/SslTransportServerTest.java
new file mode 100644
index 0000000000..c1e4cafad5
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/SslTransportServerTest.java
@@ -0,0 +1,94 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.net.URI;
+
+public class SslTransportServerTest extends TestCase {
+ private SslTransportServer sslTransportServer = null;
+ private StubSSLServerSocket sslServerSocket = null;
+
+ protected void setUp() throws Exception {
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ private void createAndBindTransportServer(boolean wantClientAuth, boolean needClientAuth, String options)
+ throws IOException {
+ sslServerSocket = new StubSSLServerSocket();
+
+ StubSSLSocketFactory socketFactory = new StubSSLSocketFactory(sslServerSocket);
+
+ try {
+ sslTransportServer =
+ new SslTransportServer(null, new URI("ssl://localhost:61616?" + options), socketFactory);
+ } catch (Exception e) {
+ fail("Unable to create SslTransportServer.");
+ }
+
+ sslTransportServer.setWantClientAuth(wantClientAuth);
+ sslTransportServer.setNeedClientAuth(needClientAuth);
+
+ sslTransportServer.bind();
+ }
+
+ public void testWantAndNeedClientAuthSetters() throws IOException {
+ for (int i = 0; i < 4; ++i) {
+ final boolean wantClientAuth = ((i & 0x1) == 1);
+ final boolean needClientAuth = ((i & 0x2) == 1);
+
+ final int expectedWantStatus = (wantClientAuth ? StubSSLServerSocket.TRUE : StubSSLServerSocket.FALSE );
+ final int expectedNeedStatus = (needClientAuth ? StubSSLServerSocket.TRUE : StubSSLServerSocket.FALSE );
+
+ createAndBindTransportServer(wantClientAuth, needClientAuth, "");
+
+ assertEquals("Created ServerSocket did not have correct wantClientAuth status.",
+ sslServerSocket.getWantClientAuthStatus(), expectedWantStatus);
+
+ assertEquals("Created ServerSocket did not have correct needClientAuth status.",
+ sslServerSocket.getNeedClientAuthStatus(), expectedNeedStatus);
+ }
+ }
+
+ public void testWantAndNeedAuthReflection() throws IOException {
+ for (int i = 0; i < 4; ++i) {
+ final boolean wantClientAuth = ((i & 0x1) == 1);
+ final boolean needClientAuth = ((i & 0x2) == 1);
+
+ final int expectedWantStatus = (wantClientAuth ? StubSSLServerSocket.TRUE : StubSSLServerSocket.FALSE );
+ final int expectedNeedStatus = (needClientAuth ? StubSSLServerSocket.TRUE : StubSSLServerSocket.FALSE );
+
+ String options = "wantClientAuth=" + (wantClientAuth ? "true" : "false") +
+ "&needClientAuth=" + (needClientAuth ? "true" : "false");
+
+ createAndBindTransportServer(wantClientAuth, needClientAuth, options);
+
+ assertEquals("Created ServerSocket did not have correct wantClientAuth status.",
+ sslServerSocket.getWantClientAuthStatus(), expectedWantStatus);
+
+ assertEquals("Created ServerSocket did not have correct needClientAuth status.",
+ sslServerSocket.getNeedClientAuthStatus(), expectedNeedStatus);
+ }
+ }
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/transport/tcp/SslTransportTest.java b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/SslTransportTest.java
new file mode 100644
index 0000000000..07bee92e4e
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/SslTransportTest.java
@@ -0,0 +1,106 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+
+import javax.management.remote.JMXPrincipal;
+import javax.net.ssl.SSLSocket;
+
+import junit.framework.TestCase;
+
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.transport.StubTransportListener;
+import org.apache.activemq.wireformat.ObjectStreamWireFormat;
+
+/**
+ * Unit tests for the SslTransport class.
+ *
+ */
+public class SslTransportTest extends TestCase {
+
+ SSLSocket sslSocket;
+ SslTransport transport;
+ StubTransportListener stubListener;
+
+ String username;
+ String password;
+ String certDistinguishedName;
+
+ protected void setUp() throws Exception {
+ certDistinguishedName = "ThisNameIsDistinguished";
+ username = "SomeUserName";
+ password = "SomePassword";
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ private void createTransportAndConsume( boolean wantAuth, boolean needAuth ) throws IOException {
+ JMXPrincipal principal = new JMXPrincipal( certDistinguishedName );
+ X509Certificate cert = new StubX509Certificate( principal );
+ StubSSLSession sslSession =
+ new StubSSLSession( cert );
+
+ sslSocket = new StubSSLSocket( sslSession );
+ sslSocket.setWantClientAuth(wantAuth);
+ sslSocket.setNeedClientAuth(needAuth);
+
+ SslTransport transport = new SslTransport(
+ new ObjectStreamWireFormat(), sslSocket );
+
+ stubListener = new StubTransportListener();
+
+ transport.setTransportListener( stubListener );
+
+ ConnectionInfo sentInfo = new ConnectionInfo();
+
+ sentInfo.setUserName(username);
+ sentInfo.setPassword(password);
+
+ transport.doConsume(sentInfo);
+ }
+
+ public void testKeepClientUserName() throws IOException {
+ createTransportAndConsume(true, true);
+
+ final ConnectionInfo receivedInfo =
+ (ConnectionInfo) stubListener.getCommands().remove();
+
+ X509Certificate receivedCert;
+
+ try {
+ receivedCert = ((X509Certificate[])receivedInfo.getTransportContext())[0];
+ } catch (Exception e) {
+ receivedCert = null;
+ }
+
+ if ( receivedCert == null ) {
+ fail("Transmitted certificate chain was not attached to ConnectionInfo.");
+ }
+
+ assertEquals("Received certificate distinguished name did not match the one transmitted.",
+ certDistinguishedName, receivedCert.getSubjectDN().getName());
+
+ }
+}
+
+
diff --git a/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLServerSocket.java b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLServerSocket.java
new file mode 100644
index 0000000000..681d72886b
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLServerSocket.java
@@ -0,0 +1,98 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import java.io.IOException;
+
+import javax.net.ssl.SSLServerSocket;
+
+public class StubSSLServerSocket extends SSLServerSocket {
+ public static final int UNTOUCHED = -1;
+ public static final int FALSE = 0;
+ public static final int TRUE = 1;
+
+ private int wantClientAuthStatus = UNTOUCHED;
+ private int needClientAuthStatus = UNTOUCHED;
+
+ public StubSSLServerSocket() throws IOException {
+
+ }
+
+ public int getWantClientAuthStatus() {
+ return wantClientAuthStatus;
+ }
+
+ public int getNeedClientAuthStatus() {
+ return needClientAuthStatus;
+ }
+
+ public void setWantClientAuth(boolean want) {
+ wantClientAuthStatus = (want ? TRUE : FALSE);
+ }
+
+ public void setNeedClientAuth(boolean need) {
+ needClientAuthStatus = (need ? TRUE : FALSE);
+ }
+
+ // --- Stubbed methods ---
+
+ public boolean getEnableSessionCreation() {
+ return false;
+ }
+
+ public String[] getEnabledCipherSuites() {
+ return null;
+ }
+
+ public String[] getEnabledProtocols() {
+ return null;
+ }
+
+ public boolean getNeedClientAuth() {
+ return false;
+ }
+
+ public String[] getSupportedCipherSuites() {
+ return null;
+ }
+
+ public String[] getSupportedProtocols() {
+ return null;
+ }
+
+ public boolean getUseClientMode() {
+ return false;
+ }
+
+ public boolean getWantClientAuth() {
+ return false;
+ }
+
+ public void setEnableSessionCreation(boolean flag) {
+ }
+
+ public void setEnabledCipherSuites(String[] suites) {
+ }
+
+ public void setEnabledProtocols(String[] protocols) {
+ }
+
+ public void setUseClientMode(boolean mode) {
+ }
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLSession.java b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLSession.java
new file mode 100644
index 0000000000..cefb356007
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLSession.java
@@ -0,0 +1,135 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+
+class StubSSLSession implements SSLSession {
+
+ X509Certificate cert;
+ boolean isVerified = false;
+
+ public StubSSLSession(X509Certificate cert) {
+ if ( cert != null ) {
+ this.isVerified = true;
+ this.cert = cert;
+ } else {
+ this.isVerified = false;
+ this.cert = null;
+ }
+ }
+
+ public void setIsVerified( boolean verified ) {
+ this.isVerified = verified;
+ }
+
+ public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+ if ( this.isVerified )
+ return new X509Certificate[] { this.cert };
+ else
+ throw new SSLPeerUnverifiedException("Socket is unverified.");
+ }
+
+
+ // --- Stubbed methods ---
+
+ public byte[] getId() {
+ return null;
+ }
+
+ public SSLSessionContext getSessionContext() {
+ return null;
+ }
+
+ public long getCreationTime() {
+ return 0;
+ }
+
+ public long getLastAccessedTime() {
+ return 0;
+ }
+
+ public void invalidate() {
+ }
+
+ public boolean isValid() {
+ return false;
+ }
+
+ public void putValue(String arg0, Object arg1) {
+ }
+
+ public Object getValue(String arg0) {
+ return null;
+ }
+
+ public void removeValue(String arg0) {
+ }
+
+ public String[] getValueNames() {
+ return null;
+ }
+
+ public Certificate[] getLocalCertificates() {
+ return null;
+ }
+
+ public javax.security.cert.X509Certificate[] getPeerCertificateChain()
+ throws SSLPeerUnverifiedException {
+ return null;
+ }
+
+ public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+ return null;
+ }
+
+ public Principal getLocalPrincipal() {
+ return null;
+ }
+
+ public String getCipherSuite() {
+ return null;
+ }
+
+ public String getProtocol() {
+ return null;
+ }
+
+ public String getPeerHost() {
+ return null;
+ }
+
+ public int getPeerPort() {
+ return 0;
+ }
+
+ public int getPacketBufferSize() {
+ return 0;
+ }
+
+ public int getApplicationBufferSize() {
+ return 0;
+ }
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLSocket.java b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLSocket.java
new file mode 100644
index 0000000000..28fbe94feb
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLSocket.java
@@ -0,0 +1,127 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import java.io.IOException;
+
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+public class StubSSLSocket extends SSLSocket {
+
+ public static final int UNTOUCHED = -1;
+ public static final int FALSE = 0;
+ public static final int TRUE = 1;
+
+ private int wantClientAuthStatus = UNTOUCHED;
+ private int needClientAuthStatus = UNTOUCHED;
+ private int useClientModeStatus = UNTOUCHED;
+
+ final StubSSLSession session;
+
+ public StubSSLSocket(StubSSLSession ses) {
+ this.session = ses;
+ }
+
+ public void setWantClientAuth(boolean arg0) {
+ this.wantClientAuthStatus = (arg0 ? TRUE : FALSE);
+ }
+
+ public void setNeedClientAuth(boolean arg0) {
+ this.needClientAuthStatus = (arg0 ? TRUE : FALSE);
+ if ( session != null ) {
+ this.session.setIsVerified(arg0);
+ }
+ }
+
+ public void setUseClientMode(boolean arg0) {
+ useClientModeStatus = (arg0 ? TRUE : FALSE);
+ }
+
+ public boolean getWantClientAuth() {
+ return (wantClientAuthStatus == TRUE);
+ }
+
+ public boolean getNeedClientAuth() {
+ return (needClientAuthStatus == TRUE);
+ }
+
+ public boolean getUseClientMode() {
+ return (useClientModeStatus == TRUE );
+ }
+
+ public int getWantClientAuthStatus() {
+ return wantClientAuthStatus;
+ }
+
+ public int getNeedClientAuthStatus() {
+ return needClientAuthStatus;
+ }
+
+ public int getUseClientModeStatus() {
+ return useClientModeStatus;
+ }
+
+ public SSLSession getSession() {
+ return this.session;
+ }
+
+
+ // --- Stubbed methods ---
+
+ public String[] getSupportedCipherSuites() {
+ return null;
+ }
+
+ public String[] getEnabledCipherSuites() {
+ return null;
+ }
+
+ public void setEnabledCipherSuites(String[] arg0) {
+ }
+
+ public String[] getSupportedProtocols() {
+ return null;
+ }
+
+ public String[] getEnabledProtocols() {
+ return null;
+ }
+
+ public void setEnabledProtocols(String[] arg0) {
+ }
+
+ public void addHandshakeCompletedListener(HandshakeCompletedListener arg0) {
+ }
+
+ public void removeHandshakeCompletedListener(HandshakeCompletedListener arg0) {
+ }
+
+ public void startHandshake() throws IOException {
+ }
+
+ public void setEnableSessionCreation(boolean arg0) {
+ }
+
+ public boolean getEnableSessionCreation() {
+ return false;
+ }
+
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLSocketFactory.java b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLSocketFactory.java
new file mode 100644
index 0000000000..06f33161ea
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSSLSocketFactory.java
@@ -0,0 +1,59 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import javax.net.ssl.SSLServerSocketFactory;
+
+public class StubSSLSocketFactory extends SSLServerSocketFactory {
+
+ private final ServerSocket retServerSocket;
+
+ public StubSSLSocketFactory(ServerSocket returnServerSocket) {
+ retServerSocket = returnServerSocket;
+ }
+
+ public ServerSocket createServerSocket(int arg0) throws IOException {
+ return retServerSocket;
+ }
+
+ public ServerSocket createServerSocket(int arg0, int arg1)
+ throws IOException {
+ return retServerSocket;
+ }
+
+ public ServerSocket createServerSocket(int arg0, int arg1, InetAddress arg2)
+ throws IOException {
+ return retServerSocket;
+ }
+
+ // --- Stubbed Methods ---
+
+ public String[] getDefaultCipherSuites() {
+ return null;
+ }
+
+ public String[] getSupportedCipherSuites() {
+ return null;
+ }
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSslTransport.java b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSslTransport.java
new file mode 100644
index 0000000000..1d39fce7c7
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubSslTransport.java
@@ -0,0 +1,52 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import javax.net.ssl.SSLSocket;
+
+import org.apache.activemq.wireformat.WireFormat;
+
+public class StubSslTransport extends SslTransport {
+ public static final int UNTOUCHED = -1;
+ public static final int FALSE = 0;
+ public static final int TRUE = 1;
+
+ private int wantClientAuthStatus = UNTOUCHED;
+ private int needClientAuthStatus = UNTOUCHED;
+
+ public StubSslTransport(WireFormat wireFormat, SSLSocket socket) throws Exception {
+ super(wireFormat, socket);
+ }
+
+ public void setWantClientAuth(boolean arg0) {
+ this.wantClientAuthStatus = (arg0 ? TRUE : FALSE);
+ }
+
+ public void setNeedClientAuth(boolean arg0) {
+ this.needClientAuthStatus = (arg0 ? TRUE : FALSE);
+ }
+
+ public int getWantClientAuthStatus() {
+ return wantClientAuthStatus;
+ }
+
+ public int getNeedClientAuthStatus() {
+ return needClientAuthStatus;
+ }
+}
diff --git a/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubX509Certificate.java b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubX509Certificate.java
new file mode 100644
index 0000000000..9b027dc653
--- /dev/null
+++ b/activemq-core/src/test/java/org/apache/activemq/transport/tcp/StubX509Certificate.java
@@ -0,0 +1,166 @@
+/**
+ *
+ * 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.transport.tcp;
+
+import java.math.BigInteger;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.util.Date;
+import java.util.Set;
+
+import java.security.cert.X509Certificate;
+
+public class StubX509Certificate extends X509Certificate {
+ public StubX509Certificate(Principal id) {
+ this.id = id;
+ }
+
+ public Principal getSubjectDN() {
+ return this.id;
+ }
+
+ private final Principal id;
+
+ // --- Stubbed Methods ---
+ public void checkValidity() {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void checkValidity(Date arg0) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public int getVersion() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public BigInteger getSerialNumber() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Principal getIssuerDN() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Date getNotBefore() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Date getNotAfter() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public byte[] getTBSCertificate() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public byte[] getSignature() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public String getSigAlgName() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public String getSigAlgOID() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public byte[] getSigAlgParams() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public boolean[] getIssuerUniqueID() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public boolean[] getSubjectUniqueID() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public boolean[] getKeyUsage() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public int getBasicConstraints() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public byte[] getEncoded() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public void verify(PublicKey arg0) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void verify(PublicKey arg0, String arg1) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public String toString() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public PublicKey getPublicKey() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public boolean hasUnsupportedCriticalExtension() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public Set getCriticalExtensionOIDs() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Set getNonCriticalExtensionOIDs() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public byte[] getExtensionValue(String arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/activemq-jaas/src/main/java/org/apache/activemq/jaas/CertificateCallback.java b/activemq-jaas/src/main/java/org/apache/activemq/jaas/CertificateCallback.java
new file mode 100644
index 0000000000..93661eff4b
--- /dev/null
+++ b/activemq-jaas/src/main/java/org/apache/activemq/jaas/CertificateCallback.java
@@ -0,0 +1,52 @@
+/**
+ *
+ * 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.jaas;
+
+import javax.security.auth.callback.Callback;
+import java.security.cert.X509Certificate;
+
+/**
+ * A Callback for SSL certificates.
+ *
+ * Will return a certificate chain to its client.
+ *
+ * @author sepandm@gmail.com (Sepand)
+ *
+ */
+public class CertificateCallback implements Callback {
+ X509Certificate certificates[] = null;
+
+ /**
+ * Setter for certificate chain.
+ *
+ * @param certs The certificates to be returned.
+ */
+ public void setCertificates(X509Certificate certs[]) {
+ certificates = certs;
+ }
+
+ /**
+ * Getter for certificate chain.
+ *
+ * @return The certificates being carried.
+ */
+ public X509Certificate[] getCertificates() {
+ return certificates;
+ }
+}
diff --git a/activemq-jaas/src/main/java/org/apache/activemq/jaas/CertificateLoginModule.java b/activemq-jaas/src/main/java/org/apache/activemq/jaas/CertificateLoginModule.java
new file mode 100644
index 0000000000..c6484cecff
--- /dev/null
+++ b/activemq-jaas/src/main/java/org/apache/activemq/jaas/CertificateLoginModule.java
@@ -0,0 +1,191 @@
+/**
+ *
+ * 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.jaas;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.security.cert.X509Certificate;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A LoginModule that allows for authentication based on SSL certificates.
+ *
+ * Allows for subclasses to define methods used to verify user certificates and find user groups.
+ * Uses CertificateCallbacks to retrieve certificates.
+ *
+ * @author sepandm@gmail.com (Sepand)
+ *
+ */
+public abstract class CertificateLoginModule implements LoginModule {
+
+ private CallbackHandler callbackHandler;
+ private Subject subject;
+
+ private X509Certificate certificates[];
+ private String username = null;
+ private Set groups = null;
+
+ private Set principals = new HashSet();
+
+ private static final Log log = LogFactory.getLog(CertificateLoginModule.class);
+ private boolean debug;
+
+ /**
+ * Overriding to allow for proper initialization.
+ *
+ * Standard JAAS.
+ */
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+
+ debug = "true".equalsIgnoreCase((String) options.get("debug"));
+
+ if (debug) {
+ log.debug("Initialized debug");
+ }
+ }
+
+ /**
+ * Overriding to allow for certificate-based login.
+ *
+ * Standard JAAS.
+ */
+ public boolean login() throws LoginException {
+ Callback[] callbacks = new Callback[1];
+
+ callbacks[0] = new CertificateCallback();
+ try {
+ callbackHandler.handle(callbacks);
+ } catch (IOException ioe) {
+ throw new LoginException(ioe.getMessage());
+ } catch (UnsupportedCallbackException uce) {
+ throw new LoginException(uce.getMessage() + " Unable to obtain client certificates.");
+ }
+ certificates = ((CertificateCallback) callbacks[0]).getCertificates();
+
+ username = getUserNameForCertificates(certificates);
+ if ( username == null )
+ throw new FailedLoginException("Unable to verify client certificates.");
+
+ groups = getUserGroups(username);
+
+ if (debug) {
+ log.debug("Certificate for user: " + username);
+ }
+ return true;
+ }
+
+ /**
+ * Overriding to complete login process.
+ *
+ * Standard JAAS.
+ */
+ public boolean commit() throws LoginException {
+ principals.add(new UserPrincipal(username));
+
+ String currentGroup = null;
+ for (Iterator iter = groups.iterator(); iter.hasNext(); ) {
+ currentGroup = (String)iter.next();
+ principals.add(new GroupPrincipal(currentGroup));
+ }
+
+ subject.getPrincipals().addAll(principals);
+
+ clear();
+
+ if (debug) {
+ log.debug("commit");
+ }
+ return true;
+ }
+
+ /**
+ * Standard JAAS override.
+ */
+ public boolean abort() throws LoginException {
+ clear();
+
+ if (debug) {
+ log.debug("abort");
+ }
+ return true;
+ }
+
+ /**
+ * Standard JAAS override.
+ */
+ public boolean logout() {
+ subject.getPrincipals().removeAll(principals);
+ principals.clear();
+
+ if (debug) {
+ log.debug("logout");
+ }
+ return true;
+ }
+
+ /**
+ * Helper method.
+ */
+ private void clear() {
+ groups.clear();
+ certificates = null;
+ }
+
+ /**
+ * Should return a unique name corresponding to the certificates given.
+ *
+ * The name returned will be used to look up access levels as well as
+ * group associations.
+ *
+ * @param dn The distinguished name.
+ * @return The unique name if the certificate is recognized, null otherwise.
+ */
+ protected abstract String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException;
+
+ /**
+ * Should return a set of the groups this user belongs to.
+ *
+ * The groups returned will be added to the user's credentials.
+ *
+ * @param username The username of the client. This is the same name that
+ * getUserNameForDn returned for the user's DN.
+ * @return A Set of the names of the groups this user belongs to.
+ */
+ protected abstract Set getUserGroups(final String username) throws LoginException;
+
+}
diff --git a/activemq-jaas/src/main/java/org/apache/activemq/jaas/JaasCertificateCallbackHandler.java b/activemq-jaas/src/main/java/org/apache/activemq/jaas/JaasCertificateCallbackHandler.java
new file mode 100644
index 0000000000..c4314100d7
--- /dev/null
+++ b/activemq-jaas/src/main/java/org/apache/activemq/jaas/JaasCertificateCallbackHandler.java
@@ -0,0 +1,71 @@
+/**
+ *
+ * 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.jaas;
+
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+
+/**
+ * A Standard JAAS callback handler for SSL certificate requests.
+ *
+ * Will only handle callbacks of type CertificateCallback.
+ *
+ * @author sepandm@gmail.com (Sepand)
+ *
+ */
+public class JaasCertificateCallbackHandler implements CallbackHandler {
+ final X509Certificate certificates[];
+
+ /**
+ * Basic constructor.
+ *
+ * @param cert The certificate returned when calling back.
+ */
+ public JaasCertificateCallbackHandler(X509Certificate certs[]) {
+ certificates = certs;
+ }
+
+ /**
+ * Overriding handle method to handle certificates.
+ *
+ * @param callbacks The callbacks requested.
+ * @throws IOException
+ * @throws UnsupportedCallbackException Thrown if an unkown Callback type is encountered.
+ */
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (int i = 0; i < callbacks.length; i++) {
+ Callback callback = callbacks[i];
+ if (callback instanceof CertificateCallback) {
+ CertificateCallback certCallback = (CertificateCallback) callback;
+
+ certCallback.setCertificates(certificates);
+
+ } else {
+ throw new UnsupportedCallbackException(callback);
+ }
+ }
+ }
+}
diff --git a/activemq-jaas/src/main/java/org/apache/activemq/jaas/JassCredentialCallback.java b/activemq-jaas/src/main/java/org/apache/activemq/jaas/JassCredentialCallback.java
new file mode 100644
index 0000000000..990467350b
--- /dev/null
+++ b/activemq-jaas/src/main/java/org/apache/activemq/jaas/JassCredentialCallback.java
@@ -0,0 +1,63 @@
+/**
+ *
+ * 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.jaas;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import java.io.IOException;
+
+
+/**
+ * A JASS username password CallbackHandler.
+ */
+public class JassCredentialCallback implements CallbackHandler {
+
+ private final String username;
+ private final String password;
+
+ public JassCredentialCallback(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (int i = 0; i < callbacks.length; i++) {
+ Callback callback = callbacks[i];
+ if (callback instanceof PasswordCallback) {
+ PasswordCallback passwordCallback = (PasswordCallback) callback;
+ if (password == null) {
+ passwordCallback.setPassword(null);
+ }
+ else {
+ passwordCallback.setPassword(password.toCharArray());
+ }
+ } else if (callback instanceof NameCallback) {
+ NameCallback nameCallback = (NameCallback) callback;
+ if (username == null) {
+ nameCallback.setName(null);
+ }
+ else {
+ nameCallback.setName(username);
+ }
+ }
+ }
+ }
+}
diff --git a/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java b/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java
new file mode 100644
index 0000000000..d07460dd2e
--- /dev/null
+++ b/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java
@@ -0,0 +1,140 @@
+/**
+ *
+ * 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.jaas;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+
+/**
+ * A LoginModule allowing for SSL certificate based authentication based on Distinguished Names (DN) stored in text
+ * files.
+ *
+ * The DNs are parsed using a Properties class where each line is =.
+ * This class also uses a group definition file where each line is =,,etc.
+ * The user and group files' locations must be specified in the org.apache.activemq.jaas.textfiledn.user and
+ * org.apache.activemq.jaas.textfiledn.user properties respectively.
+ *
+ * NOTE: This class will re-read user and group files for every authentication (i.e it does live updates of allowed
+ * groups and users).
+ *
+ * @author sepandm@gmail.com (Sepand)
+ */
+public class TextFileCertificateLoginModule extends CertificateLoginModule {
+
+ private final String USER_FILE = "org.apache.activemq.jaas.textfiledn.user";
+ private final String GROUP_FILE = "org.apache.activemq.jaas.textfiledn.group";
+
+ private File baseDir;
+ private String usersFilePathname;
+ private String groupsFilePathname;
+
+ /**
+ * Performs initialization of file paths.
+ *
+ * A standard JAAS override.
+ */
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+ super.initialize(subject, callbackHandler, sharedState, options);
+ if (System.getProperty("java.security.auth.login.config") != null) {
+ baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile();
+ } else {
+ baseDir = new File(".");
+ }
+
+ usersFilePathname = (String) options.get(USER_FILE)+"";
+ groupsFilePathname = (String) options.get(GROUP_FILE)+"";
+ }
+
+ /**
+ * Overriding to allow DN authorization based on DNs specified in text files.
+ *
+ * @param certs The certificate the incoming connection provided.
+ * @return The user's authenticated name or null if unable to authenticate the user.
+ * @throws LoginException Thrown if unable to find user file or connection certificate.
+ */
+ protected String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException {
+ if (certs == null) {
+ throw new LoginException("Client certificates not found. Cannot authenticate.");
+ }
+
+ File usersFile = new File(baseDir,usersFilePathname);
+
+ Properties users = new Properties();
+
+ try {
+ users.load(new java.io.FileInputStream(usersFile));
+ } catch (IOException ioe) {
+ throw new LoginException("Unable to load user properties file " + usersFile);
+ }
+
+ String dn = certs[0].getSubjectDN().getName();
+
+ for(Enumeration vals = users.elements(), keys = users.keys(); vals.hasMoreElements(); ) {
+ if ( ((String)vals.nextElement()).equals(dn) ) {
+ return (String)keys.nextElement();
+ } else {
+ keys.nextElement();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Overriding to allow for group discovery based on text files.
+ *
+ * @param username The name of the user being examined. This is the same name returned by
+ * getUserNameForCertificates.
+ * @return A Set of name Strings for groups this user belongs to.
+ * @throws LoginException Thrown if unable to find group definition file.
+ */
+ protected Set getUserGroups(String username) throws LoginException {
+ File groupsFile = new File(baseDir, groupsFilePathname);
+
+ Properties groups = new Properties();
+ try {
+ groups.load(new java.io.FileInputStream(groupsFile));
+ } catch (IOException ioe) {
+ throw new LoginException("Unable to load group properties file " + groupsFile);
+ }
+ Set userGroups = new HashSet();
+ for (Enumeration enumeration = groups.keys(); enumeration.hasMoreElements();) {
+ String groupName = (String) enumeration.nextElement();
+ String[] userList = (groups.getProperty(groupName) + "").split(",");
+ for (int i = 0; i < userList.length; i++) {
+ if (username.equals(userList[i])) {
+ userGroups.add(groupName);
+ break;
+ }
+ }
+ }
+
+ return userGroups;
+ }
+}
diff --git a/activemq-jaas/src/test/java/org/apache/activemq/jaas/CertificateLoginModuleTest.java b/activemq-jaas/src/test/java/org/apache/activemq/jaas/CertificateLoginModuleTest.java
new file mode 100644
index 0000000000..f126ebb17d
--- /dev/null
+++ b/activemq-jaas/src/test/java/org/apache/activemq/jaas/CertificateLoginModuleTest.java
@@ -0,0 +1,144 @@
+/**
+ *
+ * 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.jaas;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+
+public class CertificateLoginModuleTest extends TestCase {
+ private final String userName = "testUser";
+ private final List groupNames = new Vector();
+ private StubCertificateLoginModule loginModule;
+
+ private Subject subject;
+
+ public CertificateLoginModuleTest() {
+ groupNames.add("testGroup1");
+ groupNames.add("testGroup2");
+ groupNames.add("testGroup3");
+ groupNames.add("testGroup4");
+ }
+
+ protected void setUp() throws Exception {
+ subject = new Subject();
+ }
+
+ protected void tearDown() throws Exception {
+ }
+
+ private void loginWithCredentials(String userName, Set groupNames) throws LoginException {
+ loginModule = new StubCertificateLoginModule(userName, new HashSet(groupNames));
+ JaasCertificateCallbackHandler callbackHandler = new JaasCertificateCallbackHandler(null);
+
+ loginModule.initialize(subject, callbackHandler, null, new HashMap());
+
+ loginModule.login();
+ loginModule.commit();
+ }
+
+ private void checkPrincipalsMatch(Subject subject) {
+ boolean nameFound = false;
+ boolean groupsFound[] = new boolean[groupNames.size()];
+ for (int i = 0; i < groupsFound.length; ++i) {
+ groupsFound[i] = false;
+ }
+
+ for (Iterator iter = subject.getPrincipals().iterator(); iter.hasNext(); ) {
+ Principal currentPrincipal = (Principal) iter.next();
+
+ if (currentPrincipal instanceof UserPrincipal) {
+ if (((UserPrincipal)currentPrincipal).getName().equals(userName)) {
+ if (nameFound == false) {
+ nameFound = true;
+ } else {
+ fail("UserPrincipal found twice.");
+ }
+
+ } else {
+ fail("Unknown UserPrincipal found.");
+ }
+
+ } else if (currentPrincipal instanceof GroupPrincipal) {
+ int principalIdx = groupNames.indexOf(((GroupPrincipal)currentPrincipal).getName());
+
+ if (principalIdx < 0) {
+ fail("Unknown GroupPrincipal found.");
+ }
+
+ if (groupsFound[principalIdx] == false) {
+ groupsFound[principalIdx] = true;
+ } else {
+ fail("GroupPrincipal found twice.");
+ }
+ } else {
+ fail("Unknown Principal type found.");
+ }
+ }
+ }
+
+ public void testLoginSuccess() throws IOException {
+ try {
+ loginWithCredentials(userName, new HashSet(groupNames));
+ } catch (Exception e) {
+ fail("Unable to login: " + e.getMessage());
+ }
+
+ checkPrincipalsMatch(subject);
+ }
+
+ public void testLoginFailure() throws IOException {
+ boolean loginFailed = false;
+
+ try {
+ loginWithCredentials(null, new HashSet());
+ } catch (LoginException e) {
+ loginFailed = true;
+ }
+
+ if (!loginFailed) {
+ fail("Logged in with unknown certificate.");
+ }
+ }
+
+ public void testLogOut() throws IOException {
+ try {
+ loginWithCredentials(userName, new HashSet(groupNames));
+ } catch (Exception e) {
+ fail("Unable to login: " + e.getMessage());
+ }
+
+ loginModule.logout();
+
+ assertEquals("logout should have cleared Subject principals.", 0, subject.getPrincipals().size());
+ }
+}
+
diff --git a/activemq-jaas/src/test/java/org/apache/activemq/jaas/StubCertificateLoginModule.java b/activemq-jaas/src/test/java/org/apache/activemq/jaas/StubCertificateLoginModule.java
new file mode 100644
index 0000000000..38401a6410
--- /dev/null
+++ b/activemq-jaas/src/test/java/org/apache/activemq/jaas/StubCertificateLoginModule.java
@@ -0,0 +1,48 @@
+/**
+ *
+ * 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.jaas;
+
+import java.security.cert.X509Certificate;
+import java.util.Set;
+
+import javax.security.auth.login.LoginException;
+
+public class StubCertificateLoginModule extends CertificateLoginModule {
+ final String userName;
+ final Set groupNames;
+
+ String lastUserName = null;
+ X509Certificate[] lastCertChain = null;
+
+ public StubCertificateLoginModule(String userName, Set groupNames) {
+ this.userName = userName;
+ this.groupNames = groupNames;
+ }
+
+ protected String getUserNameForCertificates(X509Certificate[] certs)
+ throws LoginException {
+ lastCertChain = certs;
+ return userName;
+ }
+
+ protected Set getUserGroups(String username) throws LoginException {
+ lastUserName = username;
+ return this.groupNames;
+ }
+}