mirror of https://github.com/apache/activemq.git
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:
parent
4f945cf7e1
commit
1a88ac6ac3
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue