Commiting awesome patch by Sepand Mavandadi to add ActiveMQ support for SSL authentication and authorization: https://issues.apache.org/activemq/browse/AMQ-912

git-svn-id: https://svn.apache.org/repos/asf/incubator/activemq/trunk@447608 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Hiram R. Chirino 2006-09-18 22:43:24 +00:00
parent 4f945cf7e1
commit 1a88ac6ac3
29 changed files with 2990 additions and 0 deletions

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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 <b>java.security.auth.login.config</b> system property
* is not defined then it is set to the location of the <b>login.config</b> 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);
}
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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 {
}
}

View File

@ -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() {
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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 <user_name>=<user_DN>.
* This class also uses a group definition file where each line is <group_name>=<user_name_1>,<user_name_2>,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;
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}