Add support for setting HostnameVerifier for CAS validation filters in
web.xml.
This commit is contained in:
Marvin S. Addison 2010-02-24 14:55:51 +00:00
parent 96a31f5afd
commit 4ef10cd168
13 changed files with 300 additions and 13 deletions

View File

@ -0,0 +1,34 @@
/*
$Id$
Copyright (C) 2008-2009 Virginia Tech.
All rights reserved.
SEE LICENSE FOR MORE INFORMATION
Author: Middleware
Email: middleware@vt.edu
Version: $Revision$
Updated: $Date$
*/
package org.jasig.cas.client.ssl;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* Hostname verifier that performs no host name verification for an SSL peer
* such that all hosts are allowed.
*
* @author Middleware
* @version $Revision$
*
*/
public class AnyHostnameVerifier implements HostnameVerifier {
/** {@inheritDoc} */
public boolean verify(final String hostname, final SSLSession session) {
return true;
}
}

View File

@ -0,0 +1,49 @@
/*
$Id$
Copyright (C) 2008-2009 Virginia Tech.
All rights reserved.
SEE LICENSE FOR MORE INFORMATION
Author: Middleware
Email: middleware@vt.edu
Version: $Revision$
Updated: $Date$
*/
package org.jasig.cas.client.ssl;
import java.util.regex.Pattern;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* Validates an SSL peer's hostname using a regular expression that a candidate
* host must match in order to be verified.
*
* @author Middleware
* @version $Revision$
*
*/
public class RegexHostnameVerifier implements HostnameVerifier {
/** Allowed hostname pattern */
private Pattern pattern;
/**
* Creates a new instance using the given regular expression.
*
* @param regex Regular expression describing allowed hosts.
*/
public RegexHostnameVerifier(final String regex) {
this.pattern = Pattern.compile(regex);
}
/** {@inheritDoc} */
public boolean verify(final String hostname, final SSLSession session) {
return pattern.matcher(hostname).matches();
}
}

View File

@ -0,0 +1,61 @@
/*
$Id$
Copyright (C) 2008-2009 Virginia Tech.
All rights reserved.
SEE LICENSE FOR MORE INFORMATION
Author: Middleware
Email: middleware@vt.edu
Version: $Revision$
Updated: $Date$
*/
package org.jasig.cas.client.ssl;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* Verifies a SSL peer host name based on an explicit whitelist of allowed hosts.
*
* @author Middleware
* @version $Revision$
*
*/
public class WhitelistHostnameVerifier implements HostnameVerifier {
/** Allowed hosts */
private String[] allowedHosts;
/**
* Creates a new instance using the given array of allowed hosts.
*
* @param allowed Array of allowed hosts.
*/
public WhitelistHostnameVerifier(final String[] allowed) {
this.allowedHosts = allowed;
}
/**
* Creates a new instance using the given list of allowed hosts.
*
* @param allowedList Comma-separated list of allowed hosts.
*/
public WhitelistHostnameVerifier(final String allowedList) {
this.allowedHosts = allowedList.split(",\\s*");
}
/** {@inheritDoc} */
public boolean verify(final String hostname, final SSLSession session) {
for (int i = 0; i < this.allowedHosts.length; i++) {
if (hostname.equalsIgnoreCase(this.allowedHosts[i])) {
return true;
}
}
return false;
}
}

View File

@ -9,14 +9,16 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.net.URL;
import java.net.HttpURLConnection;
@ -274,10 +276,23 @@ public final class CommonUtils {
* @return the response.
*/
public static String getResponseFromServer(final URL constructedUrl) {
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) constructedUrl.openConnection();
return getResponseFromServer(constructedUrl, HttpsURLConnection.getDefaultHostnameVerifier());
}
/**
* Contacts the remote URL and returns the response.
*
* @param constructedUrl the url to contact.
* @param hostnameVerifier Host name verifier to use for HTTPS connections.
* @return the response.
*/
public static String getResponseFromServer(final URL constructedUrl, final HostnameVerifier hostnameVerifier) {
URLConnection conn = null;
try {
conn = constructedUrl.openConnection();
if (conn instanceof HttpsURLConnection) {
((HttpsURLConnection)conn).setHostnameVerifier(hostnameVerifier);
}
final BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
@ -294,13 +309,12 @@ public final class CommonUtils {
LOG.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
if (conn != null) {
conn.disconnect();
if (conn != null && conn instanceof HttpURLConnection) {
((HttpURLConnection)conn).disconnect();
}
}
}
/**
* Contacts the remote URL and returns the response.
*

View File

@ -8,10 +8,6 @@ package org.jasig.cas.client.validation;
import org.jasig.cas.client.util.CommonUtils;
import java.net.URL;
import java.net.HttpURLConnection;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
/**
* Abstract class that knows the protocol for validating a CAS ticket.
@ -30,6 +26,10 @@ public abstract class AbstractCasProtocolUrlBasedTicketValidator extends Abstrac
* Retrieves the response from the server by opening a connection and merely reading the response.
*/
protected final String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
return CommonUtils.getResponseFromServer(validationUrl);
if (this.hostnameVerifier != null) {
return CommonUtils.getResponseFromServer(validationUrl, this.hostnameVerifier);
} else {
return CommonUtils.getResponseFromServer(validationUrl);
}
}
}

View File

@ -8,6 +8,7 @@ package org.jasig.cas.client.validation;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import javax.net.ssl.HostnameVerifier;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
@ -16,6 +17,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Constructor;
/**
* The filter that handles all the work of validating ticket requests.
@ -55,9 +57,39 @@ public abstract class AbstractTicketValidationFilter extends AbstractCasFilter {
* @param filterConfig the FilterConfiguration that may be needed to construct a validator.
* @return the ticket validator.
*/
protected TicketValidator getTicketValidator(FilterConfig filterConfig) {
protected TicketValidator getTicketValidator(final FilterConfig filterConfig) {
return this.ticketValidator;
}
/**
* Gets the configured {@link HostnameVerifier} to use for HTTPS connections
* if one is configured for this filter.
* @param filterConfig Servlet filter configuration.
* @return Instance of specified host name verifier or null if none specified.
*/
protected HostnameVerifier getHostnameVerifier(final FilterConfig filterConfig) {
final String className = getPropertyFromInitParams(filterConfig, "hostnameVerifier", null);
log.trace("Using hostnameVerifier parameter: " + className);
final String config = getPropertyFromInitParams(filterConfig, "hostnameVerifierConfig", null);
log.trace("Using hostnameVerifierConfig parameter: " + config);
HostnameVerifier verifier = null;
if (className != null) {
try {
final Class verifierClass = Class.forName(className);
if (config != null) {
final Constructor cons = verifierClass.getConstructor(new Class[] {String.class});
verifier = (HostnameVerifier) cons.newInstance(new Object[] {config});
} else {
verifier = (HostnameVerifier) verifierClass.newInstance();
}
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Invalid HostnameVerifier class " + className);
} catch (Exception e) {
throw new IllegalArgumentException("Error creating instance of " + className, e);
}
}
return verifier;
}
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
setExceptionOnValidationFailure(parseBoolean(getPropertyFromInitParams(filterConfig, "exceptionOnValidationFailure", "true")));

View File

@ -17,6 +17,8 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
/**
* Abstract validator implementation for tickets that must be validated against a server.
*
@ -30,6 +32,11 @@ public abstract class AbstractUrlBasedTicketValidator implements TicketValidator
* Commons Logging instance.
*/
protected final Log log = LogFactory.getLog(getClass());
/**
* Hostname verifier used when making an SSL request to the CAS server.
*/
protected HostnameVerifier hostnameVerifier;
/**
* Prefix for the CAS server. Should be everything up to the url endpoint, including the /.
@ -198,4 +205,8 @@ public abstract class AbstractUrlBasedTicketValidator implements TicketValidator
public void setCustomParameters(final Map customParameters) {
this.customParameters = customParameters;
}
public void setHostnameVerifier(final HostnameVerifier verifier) {
this.hostnameVerifier = verifier;
}
}

View File

@ -22,6 +22,7 @@ public class Cas10TicketValidationFilter extends AbstractTicketValidationFilter
final String casServerUrlPrefix = getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null);
final Cas10TicketValidator validator = new Cas10TicketValidator(casServerUrlPrefix);
validator.setRenew(parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false")));
validator.setHostnameVerifier(getHostnameVerifier(filterConfig));
return validator;
}

View File

@ -124,6 +124,7 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal
}
validator.setCustomParameters(additionalParameters);
validator.setHostnameVerifier(getHostnameVerifier(filterConfig));
return validator;
}

View File

@ -29,6 +29,7 @@ public class Saml11TicketValidationFilter extends AbstractTicketValidationFilter
final String tolerance = getPropertyFromInitParams(filterConfig, "tolerance", "1000");
validator.setTolerance(Long.parseLong(tolerance));
validator.setRenew(parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false")));
validator.setHostnameVerifier(getHostnameVerifier(filterConfig));
return validator;
}
}

View File

@ -16,6 +16,8 @@ import java.util.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import javax.net.ssl.HttpsURLConnection;
/**
* TicketValidator that can understand validating a SAML artifact. This includes the SOAP request/response.
*
@ -175,6 +177,9 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator
try {
conn = (HttpURLConnection) validationUrl.openConnection();
if (this.hostnameVerifier != null && conn instanceof HttpsURLConnection) {
((HttpsURLConnection)conn).setHostnameVerifier(this.hostnameVerifier);
}
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "text/xml");
conn.setRequestProperty("Content-Length", Integer.toString(MESSAGE_TO_SEND.length()));

View File

@ -0,0 +1,39 @@
/*
$Id$
Copyright (C) 2008-2009 Virginia Tech.
All rights reserved.
SEE LICENSE FOR MORE INFORMATION
Author: Middleware
Email: middleware@vt.edu
Version: $Revision$
Updated: $Date$
*/
package org.jasig.cas.client.ssl;
import junit.framework.Assert;
import junit.framework.TestCase;
/**
* Unit test for {@link RegexHostnameVerifier} class.
*
* @author Middleware
* @version $Revision$
*
*/
public class RegexHostnameVerifierTests extends TestCase {
/**
* Test method for {@link RegexHostnameVerifier#verify(String, SSLSession)}.
*/
public void testVerify() {
final RegexHostnameVerifier verifier = new RegexHostnameVerifier("\\w+\\.vt\\.edu");
Assert.assertTrue(verifier.verify("a.vt.edu", null));
Assert.assertTrue(verifier.verify("host.vt.edu", null));
Assert.assertFalse(verifier.verify("1-host.vt.edu", null));
Assert.assertFalse(verifier.verify("mallory.example.com", null));
}
}

View File

@ -0,0 +1,39 @@
/*
$Id$
Copyright (C) 2008-2009 Virginia Tech.
All rights reserved.
SEE LICENSE FOR MORE INFORMATION
Author: Middleware
Email: middleware@vt.edu
Version: $Revision$
Updated: $Date$
*/
package org.jasig.cas.client.ssl;
import junit.framework.Assert;
import junit.framework.TestCase;
/**
* Unit test for {@link WhitelistHostnameVerifier} class.
*
* @author Middleware
* @version $Revision$
*
*/
public class WhitelistHostnameVerifierTests extends TestCase {
/**
* Test method for {@link WhitelistHostnameVerifier#verify(String, SSLSession)}.
*/
public void testVerify() {
final WhitelistHostnameVerifier verifier = new WhitelistHostnameVerifier(
"red.vt.edu, green.vt.edu,blue.vt.edu");
Assert.assertTrue(verifier.verify("red.vt.edu", null));
Assert.assertTrue(verifier.verify("green.vt.edu", null));
Assert.assertTrue(verifier.verify("blue.vt.edu", null));
Assert.assertFalse(verifier.verify("purple.vt.edu", null));
}
}