/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.security.srp;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.MarshalledInvocation;
import org.jboss.naming.NonSerializableFactory;
import org.jboss.security.srp.SRPRemoteServer;
import org.jboss.security.srp.SRPServerListener;
import org.jboss.security.srp.SRPServerInterface;
import org.jboss.security.srp.SRPServerSession;
import org.jboss.security.srp.SRPVerifierStore;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.util.CachePolicy;
import org.jboss.util.TimedCachePolicy;
/** The JMX mbean interface for the SRP service. This mbean sets up an
RMI implementation of the 'Secure Remote Password' cryptographic authentication
system described in RFC2945.
@author Scott.Stark@jboss.org
@version $Revision: 81038 $
*/
public class SRPService extends ServiceMBeanSupport
implements SRPServiceMBean, SRPServerListener
{
/**
* @supplierRole RMI Access
* @supplierCardinality 1
* @clientCardinality 1
* @clientRole service mangement
*/
private SRPRemoteServer server;
private int serverPort = 10099;
/**
* @supplierRole password store
* @supplierCardinality 1
* @clientRole configures
*/
private SRPVerifierStore verifierStore;
private String verifierSourceJndiName = "srp/DefaultVerifierSource";
private String serverJndiName = "srp/SRPServerInterface";
private String cacheJndiName = "srp/AuthenticationCache";
private CachePolicy cachePolicy;
private int cacheTimeout = 1800;
private int cacheResolution = 60;
/** A flag indicating if a successful user auth for an existing session
should overwrite the current session.
*/
private boolean overwriteSessions;
/** A flag indicating if an aux challenge must be presented in verify */
private boolean requireAuxChallenge;
/** An optional custom client socket factory */
private RMIClientSocketFactory clientSocketFactory;
/** An optional custom server socket factory */
private RMIServerSocketFactory serverSocketFactory;
/** The class name of the optional custom client socket factory */
private String clientSocketFactoryName;
/** The class name of the optional custom server socket factory */
private String serverSocketFactoryName;
/** A <Long,Method> mapping of the SRPRemoteServerInterface */
private Map marshalledInvocationMapping = new HashMap();
// --- Begin SRPServiceMBean interface methods
/** Get the jndi name for the SRPVerifierSource implementation binding.
*/
public String getVerifierSourceJndiName()
{
return verifierSourceJndiName;
}
/** set the jndi name for the SRPVerifierSource implementation binding.
*/
public void setVerifierSourceJndiName(String jndiName)
{
this.verifierSourceJndiName = jndiName;
}
/** Get the jndi name under which the SRPServerInterface proxy should be bound
*/
public String getJndiName()
{
return serverJndiName;
}
/** Set the jndi name under which the SRPServerInterface proxy should be bound
*/
public void setJndiName(String jndiName)
{
this.serverJndiName = jndiName;
}
/** Get the jndi name under which the SRPServerInterface proxy should be bound
*/
public String getAuthenticationCacheJndiName()
{
return cacheJndiName;
}
/** Set the jndi name under which the SRPServerInterface proxy should be bound
*/
public void setAuthenticationCacheJndiName(String jndiName)
{
this.cacheJndiName = jndiName;
}
/** Get the auth cache timeout period in seconds
*/
public int getAuthenticationCacheTimeout()
{
return cacheTimeout;
}
/** Set the auth cache timeout period in seconds
*/
public void setAuthenticationCacheTimeout(int timeoutInSecs)
{
this.cacheTimeout = timeoutInSecs;
}
/** Get the auth cache resolution period in seconds
*/
public int getAuthenticationCacheResolution()
{
return cacheResolution;
}
/** Set the auth cache resolution period in seconds
*/
public void setAuthenticationCacheResolution(int resInSecs)
{
this.cacheResolution = resInSecs;
}
public boolean getRequireAuxChallenge()
{
return this.requireAuxChallenge;
}
public void setRequireAuxChallenge(boolean flag)
{
this.requireAuxChallenge = flag;
}
public boolean getOverwriteSessions()
{
return this.overwriteSessions;
}
public void setOverwriteSessions(boolean flag)
{
this.overwriteSessions = flag;
}
/** Get the RMIClientSocketFactory implementation class. If null the default
RMI client socket factory implementation is used.
*/
public String getClientSocketFactory()
{
return serverSocketFactoryName;
}
/** Set the RMIClientSocketFactory implementation class. If null the default
RMI client socket factory implementation is used.
*/
public void setClientSocketFactory(String factoryClassName)
throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
this.clientSocketFactoryName = factoryClassName;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class clazz = loader.loadClass(clientSocketFactoryName);
clientSocketFactory = (RMIClientSocketFactory) clazz.newInstance();
}
/** Get the RMIServerSocketFactory implementation class. If null the default
RMI server socket factory implementation is used.
*/
public String getServerSocketFactory()
{
return serverSocketFactoryName;
}
/** Set the RMIServerSocketFactory implementation class. If null the default
RMI server socket factory implementation is used.
*/
public void setServerSocketFactory(String factoryClassName)
throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
this.serverSocketFactoryName = factoryClassName;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class clazz = loader.loadClass(serverSocketFactoryName);
serverSocketFactory = (RMIServerSocketFactory) clazz.newInstance();
}
/** Get the RMI port for the SRPServerInterface
*/
public int getServerPort()
{
return serverPort;
}
/** Get the RMI port for the SRPServerInterface
*/
public void setServerPort(int serverPort)
{
this.serverPort = serverPort;
}
// --- End SRPServiceMBean interface methods
/** Called when username has sucessfully completed the SRP login. This
places the SRP session into the credential cache using a
SimplePrincipal based on the username as the key.
*/
public void verifiedUser(SRPSessionKey key, SRPServerSession session)
{
try
{
synchronized( cachePolicy )
{
// We only insert a principal if there is no current entry.
if( cachePolicy.peek(key) == null )
{
cachePolicy.insert(key, session);
log.trace("Cached SRP session for user="+key);
}
else if( overwriteSessions )
{
cachePolicy.remove(key);
cachePolicy.insert(key, session);
log.trace("Replaced SRP session for user="+key);
}
else
{
log.debug("Ignoring SRP session due to existing session for user="+key);
}
}
}
catch(Exception e)
{
log.error("Failed to update SRP cache for user="+key, e);
}
}
public void closedUserSession(SRPSessionKey key)
{
try
{
synchronized( cachePolicy )
{
// We only insert a principal if there is no current entry.
if( cachePolicy.peek(key) == null )
{
log.warn("No SRP session found for user="+key);
}
cachePolicy.remove(key);
}
}
catch(Exception e)
{
log.error("Failed to update SRP cache for user="+key, e);
}
}
public String getName()
{
return "SRPService";
}
public Object invoke(Invocation invocation) throws Exception
{
// Set the method hash to Method mapping
if (invocation instanceof MarshalledInvocation)
{
MarshalledInvocation mi = (MarshalledInvocation) invocation;
mi.setMethodMap(marshalledInvocationMapping);
}
// Invoke the SRPRemoteServer method via reflection
Method method = invocation.getMethod();
Object[] args = invocation.getArguments();
Object value = null;
try
{
value = method.invoke(server, args);
}
catch(InvocationTargetException e)
{
Throwable t = e.getTargetException();
if( t instanceof Exception )
throw (Exception) t;
else
throw new UndeclaredThrowableException(t, method.toString());
}
return value;
}
protected void startService() throws Exception
{
loadStore();
server = new SRPRemoteServer(verifierStore, serverPort,
clientSocketFactory, serverSocketFactory);
server.addSRPServerListener(this);
server.setRequireAuxChallenge(this.requireAuxChallenge);
// Bind a proxy to the SRPRemoteServer into jndi
InitialContext ctx = new InitialContext();
if( serverJndiName != null && serverJndiName.length() > 0 )
{
SRPServerProxy proxyHandler = new SRPServerProxy(server);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class[] interfaces = {SRPServerInterface.class};
Object proxy = Proxy.newProxyInstance(loader, interfaces, proxyHandler);
org.jboss.naming.Util.rebind(ctx, serverJndiName, proxy);
log.debug("Bound SRPServerProxy at "+serverJndiName);
}
// First check for an existing CachePolicy binding
try
{
cachePolicy = (CachePolicy) ctx.lookup(cacheJndiName);
log.debug("Found AuthenticationCache at: "+cacheJndiName);
}
catch(Exception e)
{
log.trace("Failed to find existing cache at: "+cacheJndiName, e);
// Not found, default to a TimedCachePolicy
cachePolicy = new TimedCachePolicy(cacheTimeout, true, cacheResolution);
cachePolicy.create();
cachePolicy.start();
// Bind a reference to store using NonSerializableFactory as the ObjectFactory
Name name = ctx.getNameParser("").parse(cacheJndiName);
NonSerializableFactory.rebind(name, cachePolicy, true);
log.debug("Bound AuthenticationCache at "+cacheJndiName);
}
// Build the SRPRemoteServerInterface method map
HashMap tmpMap = new HashMap(13);
Method[] methods = SRPRemoteServerInterface.class.getMethods();
for(int m = 0; m < methods.length; m ++)
{
Method method = methods[m];
Long hash = new Long(MarshalledInvocation.calculateHash(method));
tmpMap.put(hash, method);
}
marshalledInvocationMapping = Collections.unmodifiableMap(tmpMap);
}
protected void stopService() throws Exception
{
// Bind a reference to store using NonSerializableFactory as the ObjectFactory
InitialContext ctx = new InitialContext();
ctx.unbind(serverJndiName);
log.debug("Unbound SRPServerProxy at "+serverJndiName);
NonSerializableFactory.unbind(cacheJndiName);
ctx.unbind(cacheJndiName);
log.debug("Unbound AuthenticationCache at "+cacheJndiName);
}
private void loadStore() throws NamingException
{
InitialContext ctx = new InitialContext();
// Get the SRPVerifierStore implementation
verifierStore = (SRPVerifierStore) ctx.lookup(verifierSourceJndiName);
if( server != null )
{
server.setVerifierStore(verifierStore);
}
}
}