Overview
UC Berkeley uses JA-SIG CAS for its Single Sign-On (SSO) solution. Not all Web applications at UC Berkeley have been CAS-enabled. For some of the remaining applications, it is a matter of time and priority. Others, however, are considered "high value" or more security-sensitive. UC Berkeley has decided that for those applications a second-level CAS server is desired. Second-level here means that in order to authenticate to the desired secure application the users have to first authenticate to the existing CAS server (first-level), then to the second-level server before the authorized user is allowed access to the high-security Web application.
Requirements
In order to make primary CAS the first authentication source, the second-level CAS server is itself CAS-enabled. That is, in order to log in to second-level CAS, the user must successfully authenticate to the primary CAS server. It is only from primary CAS that second-level CAS can obtain the NetID, or CalNet ID, as it is known at UC Berkeley. CalNet ID is not prompted for by the second-level CAS. The only prompt issued by the second-level CAS is for CalNetKey.
For added security, CalNetKey may only be entered by clicking the mouse on a numeric keypad. This is to prevent any keystroke logging application that may have been installed on a publicly-accessible computer from logging, and potentially compromising, CalNetKey. Since CalNet ID is not exchanged over the network between the browser and second-level CAS, even in case of a compromise, an attacker would not have enough information to steal a full set of credentials required for login to high-security applications.
While the first-level CAS uses Kerberos for authentication, the second-level CAS server uses a Personal Identification Number (PIN), known at UC Berkeley as CalNetKey. CalNetKey is stored as a custom attribute in LDAP. For security reasons CalNetKey is not stored in LDAP in clear text. Instead, it is combined with a "salt" value and one-way-hash encoded into a random-looking string of alphanumeric characters. Only persons with the privilege to use the high-security Web applications have CalNetKey attributes in their LDAP records.
Primary CAS server supports the CAS Single Sign-Out feature. This means that this solution must expect and process sign-out notifications from the primary CAS server and invalidate the corresponding Single Sign-On sessions on the second-level CAS server.
UC Berkeley uses server clustering to assure high availability of its CAS servers. The solution that Unicon delivers must support clustering.
UC Berkeley wishes to explore the possibility of contributing the results of this effort to the JA-SIG open source community and beyond. Unicon is requested to evaluate the feasibility and method of making such a contribution. It is also because of this requirement that Unicon should consider this solution to be flexible enough for other institutions to adopt.
Design
General
Although UC Berkeley requirements do not explicitly specify it, Unicon desires to implement second-level CAS with the fewest modifications to the original CAS. This should make upgrading to new releases of CAS server easier for UC Berkeley after Unicon has delivered the finished product. Since CAS, and UC Berkeley's second-level CAS especially, are security applications, it will be important for UC Berkeley to quickly and with little effort incorporate new releases of CAS. This will be especially important when security-related CAS releases become available.
This is accomplished by keeping UC Berkeley-specific code in its own module in keeping with the CAS server project layout. This add-on module can be dropped into a CAS project, and with a few configuration file modifications it can be added to the Maven build process for inclusion in the ready-to-deploy CAS server package.
Integration with the Primary CAS Server
Naturally, the primary CAS server at UCB requires no modifications in this design. In order to assure primary authentication, the Second-level CAS server is CAS-enabled using the JA-SIG Java CAS client. Unicon initially chose the Yale CAS client because of its simplicity and the fact that it is bundled with CAS server. The Yale CAS client, however, does not have support for Single Sign-Out, which is required in this project.
JA-SIG Java CAS client operates as a standard JEE Web filter. Actually, it is a collection of filters that operate in unison. One assures that an authenticated session exists, another is used to validate service tickets, and another handles Single Sign-Out. All of the configuration for the JA-SIG Java CAS client resides in the web.xml file for the Web application being CAS-enabled, in this case the Second-level CAS server. To simplify how the Second-level CAS obtains the CalNet ID from the CAS filter, the filter is configured to "wrap" access to the HttpServletRequest. This allows Secondary CAS server to get the CalNet ID by simply making a getRemoteUser() call.
The Primary CAS Server supports service registry, and the Second-level CAS has been added to its list of services authorized to request authentication. Additionally, Primary CAS support the CAS Single Sign-Out feature, which notifies Second-level CAS when the user has logged out of Primary CAS.
A detailed configuration example for the JA-SIG Java CAS client is available below in the section Second-Level CAS Server documentation#Files Modified.
New User Interface
UC Berkeley developed a prototype UI for the collection of CalNetKey. This prototype was successfully incorporated into CAS 3.1. JavaScript used to implement collection of the PIN from the button clicks to the input field was incorporated and modified to refer to the input form's names as they are known to the standard CAS server.
As of CAS 3.2, the UC Berkeley-specific user interface, implemented in JSP as a Spring Webflow View, does not replace the original CAS login view. It is instead added in its own JSP file and activated by modifying the view configuration file, default_views.properties.
Credentials Interface Implementation
Since Second-level CAS collects the credentials from the JA-SIG Java CAS filter and the Web form, a new Java class, CalNetKeyCredentials, was added. To keep this implementation as simple as possible, it extends the standard UsernamePasswordCredentials implementation.
To properly extract the credentials from the CAS filter and from the new UI, a new class, CalNetKeyCredentialsBinder, was added that implements the required CredentialsBinder interface. It is this binder that actually calls getRemoteUser(), which, in turn, calls the JA-SIG Java CAS filter, to obtain the CalNet ID that came from Primary CAS authentication. In order to configure CAS to make use of this CalNetKeyCredentialsBinder, the authenticationViaFormAction bean, as defined in cas-servlet.xml, got a new property, a reference to a new bean, calNetKeyCredentialsBinder, also defined in the same file. calNetKeyCredentialsBinder bean is an instance of CalNetKeyCredentialsBinder class. authenticationViaFormAction bean also received another property, formObjectClass, which tell the bean which class to use to store credentials in. The default class was UsernamePasswordCredentials, and the newly configured class is CalNetKeyCredentials. This approach allows to keep using the CAS server-supplied AuthenticationViaFormAction class, which would be complicated to replace.
Because of the different class implementing Credentials, a new class that can resolve a principal from the new Credentials implementation was added. Its name is CalNetKeyCredentialsToPrincipalResolver, and it is configured as one of the credentialsToPrincipalResolvers properties of the authenticationManager bean in deployerConfigContext.xml. authenticationManager also is configured to use a new authentication handler in its authenticationHandlers list.
Single Sign-Out Processing
In order to process Single Sign-Out (SSOut) messages from the primary CAS server, the SSOut filter from JA-SIG CAS Client is used. This assures that the successful Primary CAS authentication artifact is removed from the filter's session and that on subsequent requests to the second-level CAS Primary CAS authentication will be enforced.
However, the core functionality of that filter was insufficient. It was also necessary to keep track of the relationships between Primary CAS service tickets (ST) and second-level CAS ticket granting tickets (TGT). Additional Web filter was designed to use an approach similar to that in the JA-SIG CAS Client's SSOut filter. This approach uses a mapping between those tickets. The mapping can retrieve ST given TGT and vice versa.
This two-way mapping is necessary because second-level CAS needs to remove the mapping if its TGT becomes invalidated because of time-out or explicit user logout.
The implementation of this mapping must function in clustered environment. Unicon has developed a simple, memory-based mapping class based on the Java Map interface. After testing based on this simpler implementation was successful, a cluster-friendly version, based on JBoss Cache, was added. The simple, memory-based implementation is available and because it comes without networking overhead, its performance should be better.
A good way to associate Primary CAS ST with second-level CAS TGT is to monitor the second-level CAS TGT creation and destruction. This is implemented in a new EventHandler registered to receive TicketEvents.
EventHandler and TicketEvent interfaces were removed from CAS 3.2. Their original intent was to help with auditing, but there is a new and more flexible replacement package for auditing. Well, in order for the Second-Level CAS to continue functioning in CAS 3.2 and beyond, it will need to monitor those events. So, this module reintroduces the entire org.jasig.cas.event package as an extension. Only slight modifications to this code were necessary to make it work correctly in CAS 3.2.
Authentication Handler
A new authentication handler, implemented by a class called CalNetKeyAuthenticationHandler, is where the actual CalNetKey authentication is performed. The handler performs a search for the LDAP entry where uid equals CalNet ID. CalNet ID is what the Primary CAS authentication provides. Only two attributes are asked for in this LDAP query: berkeleyEduCalNetKey, and berkeleyEduCalNetKeySalt. Names of these attributes are configurable as properties of the authentication handler bean configured in deployerConfigContext.xml. Here is a more complete list of properties of this bean:
Property |
Description |
Example Value |
---|---|---|
filter |
LDAP search filter |
uid=%u |
searchBase |
LDAP search base |
ou=people,dc=berkeley,dc=edu |
scope |
This is from the JNDI API used for LDAP searching. SearchControls.OBJECT_SCOPE=0, SearchControls.ONELEVEL_SCOPE=1, SearchControls.SUBTREE_SCOPE=2 |
1 |
allowMultipleAccounts |
Are multiple LDAP results permitted for this search? In the context of this project multiple results from this search would create ambiguity, so it is not anticipated that this would ever be a valid situation. |
false |
pinAttribute |
Name of the LDAP attribute storing the encoded CalNetKey |
berkeleyEduCalNetKey |
saltAttribute |
Name of the LDAP attribute storing the CalNetKey salt value used to one-way-hash encode the CalNetKey |
berkeleyEduCalNetKeySalt |
contextSource |
A reference to a bean that provides LDAP access at runtime. This is an instance of the CAS server-supplied AuthenticatedLdapContextSource defined in the same file. |
contextSource |
Based on the above properties, the handler performs the LDAP search. If successful, the search returns one or more instance of CalNetKey, an inner class containing the LDAP attributes queried for. A standard Java MessageDigest class provides the implementation of the SHA-256 hash, which is given the CalNetKey (the PIN entered by the user on the Second-level CAS authentication screen) and the salt (obtained from LDAP) to create a hash. This hash is then converted to a string to hexadecimal characters and finally compared to the encoded LDAP attribute. When they are equal, second-level authentication succeeds.
While this authentication handler is UC Berkeley-specific, any CAS authentication handler can be used to perform second-level authentication.
Files Added
This section lists files added to a standard distribution of JA-SIG CAS Server 3.1.x to implement it as Second-level CAS at UCB.
Name |
Location |
Purpose |
---|---|---|
cas-client-core-3.1.3.jar |
cas-server-webapp/target/cas-server-webapp-3.2.1.1/WEB-INF/lib* |
JA-SIG CAS Client |
CalNetKeyAuthenticationHandler.java |
cas-server-support-second-level/src/main/java/edu/berkeley/cas/adaptors/ldap |
CalNetKey CAS authentication handler |
CalNetKeyCredentials.java |
cas-server-support-second-level/src/main/java/edu/berkeley/cas/authentication/principal |
A CAS Credentials interface implementation to store CalNetKey credentials. This class extends the UsernamePasswordCredentials class. |
CalNetKeyCredentialsToPrincipalResolver.java |
cas-server-support-second-level/src/main/java/edu/berkeley/cas/authentication/principal |
A CAS CredentialsToPrincipalResolver interface implementation to retrieve a Principal from CalNetKeyCredentials. This class extends the AbstractPersonDirectoryCredentialsToPrincipalResolver class. |
CalNetKeyCredentialsBinder.java |
cas-server-support-second-level/src/main/java/edu/berkeley/cas/web/bind |
A CAS CredentialsBinder interface implementation to store the CalNetKey credentials in a CalNetKeyCredentials class. |
TicketHandler.java |
cas-server-support-second-level/src/main/java/edu/berkeley/cas/events/handler |
A CAS EventHandler implementation that tracks TGT creation and destruction |
PrimaryCASSingleSignOutFilter.java |
cas-server-support-second-level/src/main/java/edu/berkeley/cas/web/filter |
Primary CAS Single Sign-Out Web filter and related utilities |
secondLevelCAS.xml |
cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration |
Spring Beans configuration file for all second-level CAS-specific beans |
calNetKeyLoginView.jsp |
cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui |
PinPad buttons login prompt |
pinpad_styles.css |
cas-server-webapp/src/main/webapp/css |
CSS styles definition for PinPad from UCB. |
bg_button_a.gif |
cas-server-webapp/src/main/webapp/images |
Image from UCB to paint PinPad buttons. |
bg_button_span.gif |
cas-server-webapp/src/main/webapp/images |
Image from UCB to paint PinPad buttons. |
pinpad.js |
cas-server-webapp/src/main/webapp/js |
JavaScript source from UCB to implement the PinPad functionality. |
build.bat |
|
Script to perform a Maven 2 build (this script should work fine on any platform as it doesn't use platform-specific constructs). |
*This location is dynamically determined by Maven because JA-SIG Java CAS client is added as a Maven dependency.
Files Modified
Name |
Location |
Purpose |
Modification |
---|---|---|---|
default_views.properties |
cas-server-webapp/src/main/webapp/WEB-INF/classes |
CAS UI view configuration |
Changed to have CAS use calNetKeyLoginView.jsp to display login prompt |
casLogoutView.jsp |
cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui |
CAS logout confirmation |
Invalidated the HttpSession. CAS does not rely on the session, but the JA-SIG Java CAS filter does. Invalidating the session will force the filter to request primary CAS authentication. |
top.jsp |
cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui |
Common JSP headers |
Changed the title. |
cas-servlet.xml |
cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration |
Spring bean configuration file that configures the business logic processing HTTP requests. |
Added properties to the authenticationViaFormAction bean. |
deployerConfigContext.xml |
cas-server-webapp/src/main/webapp/WEB-INF |
Deployment-specific CAS server configuration file |
Replaced the default authentication handler with CalNetKeyAuthenticationHandler and its options. Added a bean with LDAP server configuration. See the example below. |
web.xml |
cas-server-webapp/src/main/webapp/WEB-INF |
Web application deployment descriptor |
Added the JA-SIG Java CAS client filters and their options as illustrated in the example below. |
pom.xml |
cas-server-webapp |
Maven 2 configuration file |
Added dependencies on the CAS LDAP module (internal), JBoss Cache module (internal), and JA-SIG Java CAS Client (external) |
pom.xml |
cas-server-support-second-level |
Maven 2 configuration file |
Added dependency on the JBoss Cache module (internal) |
log4j.properties |
cas-server-webapp/src/main/webapp/WEB-INF/classes |
Log4j configuration file |
Configured a fully-qualified path to cas.log file and added a new log file for UCB-specific logging |
web.xml addition
<!-- JA-SIG CAS client for Java --> <!-- First, the single sign-on filters --> <filter> <filter-name>CAS Authentication Filter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>https://auth-test.berkeley.edu/cas/login</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>https://aws-dev.berkeley.edu</param-value> </init-param> </filter> <filter> <filter-name>CAS Validation Filter</filter-name> <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>https://auth-test.berkeley.edu/cas</param-value> </init-param> <init-param> <param-name>redirectAfterValidation</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>https://aws-dev.berkeley.edu</param-value> </init-param> </filter> <filter> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class> </filter> <filter> <filter-name>CAS Assertion Thread Local Filter</filter-name> <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class> </filter> <!-- And now, the single sign-out filters --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <filter> <filter-name>Primary CAS Single Sign Out Filter</filter-name> <filter-class>edu.berkeley.cas.web.filter.PrimaryCASSingleSignOutFilter</filter-class> </filter> <!-- Filter mappings in the correct order --> <filter-mapping> <filter-name>Primary CAS Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>/login/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Authentication Filter</filter-name> <url-pattern>/login/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Validation Filter</filter-name> <url-pattern>/login/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <url-pattern>/login/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Assertion Thread Local Filter</filter-name> <url-pattern>/login/*</url-pattern> </filter-mapping> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener>
deployerConfigContext.xml additions
First is the new bean added to the authenticationHandlers property of the authenticationManager bean:
<bean class="edu.berkeley.cas.adaptors.ldap.CalNetKeyAuthenticationHandler"> <property name="filter" value="uid=%u" /> <property name="searchBase" value="ou=people,dc=berkeley,dc=edu" /> <!-- SearchControls.OBJECT_SCOPE=0, SearchControls.ONELEVEL_SCOPE=1, SearchControls.SUBTREE_SCOPE=2 --> <property name="scope" value="1" /> <property name="allowMultipleAccounts" value="false" /> <property name="pinAttribute" value="berkeleyEduCalNetKey" /> <property name="saltAttribute" value="berkeleyEduCalNetKeySalt" /> <property name="contextSource" ref="contextSource" /> </bean>
Second is the addition of the contextSource bean describing the LDAP server:
<bean id="contextSource" class="org.jasig.cas.adaptors.ldap.util.AuthenticatedLdapContextSource"> <property name="anonymousReadOnly" value="false" /> <property name="userName" value="uid=ist-is-ias-cas-unicon,ou=applications,dc=berkeley,dc=edu" /> <property name="password" value="[removed]" /> <property name="pooled" value="true" /> <property name="urls"> <list> <value>ldaps://ldap-test.berkeley.edu/</value> </list> </property> <property name="baseEnvironmentProperties"> <map> <entry> <key><value>java.naming.security.authentication</value></key> <value>simple</value> </entry> </map> </property> </bean>
Source Code Repository
UC Berkeley uses Subversion (SVN) as its version control system. The entire second-level CAS project has been allocated space under svn+ssh://svn.berkeley.edu/cas. Unicon created the customary "vendor," "branches," "tags," and "trunk" subdirectories in there. As is customary, the "trunk" directory will not have subdirectories, and it will reflect the most current development version of the project.
To populate this repository Unicon started by importing an unmodified release of CAS 3.1.1 into the vendor branch tagged as "current." This version was also tagged as "3.1.1." The contents of vendor branch "current" was copied into "trunk" and the development of the second-level CAS server proceeded from there. All development commits were made into "trunk."
When release 3.1.2 of CAS became available, it was imported into vendor branch using the SVN script svn_load_dirs.pl. This script automates file/directory moves, renames, and deletions with minimum risk of losing historical revisions. After the script was finished, the vendor branch "current" tas also tagged as "3.1.2." The next step was to merge the differences between 3.1.1 and 3.1.2 into the development area under trunk. The only files that were flagged as merge conflicts were GIF images that were accidentally corrupted when the release 3.1.2 of CAS was produced. The corrupted files were discarded, and the merged work area was committed to trunk.
With the CAS server release version 3.2.1.1, the vendor branch merge process was repeated. At the same time, the Second-Level CAS modifications were placed in their own module modeled after the LDAP module.
Steps Required to Install on Another Server
CAS server is packaged as a self-contained Web application archive (war file). Maven 2 is used to package this war file. For convenience, build.bat can be used to build the war file. The war file contains all the CAS configuration files, including two that will need to be changed when deploying second-level CAS to a different application server: web.xml and deployerConfigContext.xml. Example fragments of these files are included above. web.xml will need to be changed to point to the correct primary CAS server and to correctly identify the location of the second-level CAS. deployerConfigContext.xml will need to be updated with correct LDAP server address and credentials to perform LDAP searches.