Demo

The demo guide provides detailed instructions for setting up a multi-domain SSO demonstration for a quick start with CAS.


If unreadable in IE (no line wrap), try Firefox or just use the PDF utility. 

Problem Statement

You would like to show-off Single Sign On (SSO) to your boss and team ASAP. You have no clue about SSL, server certificates etc. But you know enough of Java, J2EE, Tomcat that someone is paying you for it. However, no matter how hard you try, you keep getting errors with basic CAS system install documentation.  This wiki page will show you how to install and demonstrate CAS, first on your Windows PC and then to takes a step further and shows that you can do multi-domain (another Windows PC) SSO login as well using CAS. Except for Dilbert's boss, everyone should be reasonably impressed.  If you have a team that grappled programmatically with multi-domain SSO authentication in any way shape or form they will worship you like a God. I am focussing on multi-domain because, if you have just one single tomcat instance in which you have all your applications, and would like to do SSO, you really don't need CAS at all (it would be overkill) just uncomment the following in your server.xml and you should be good to go (assuming your applications are setup to authenticate already):

<!--
     Normally, users must authenticate themselves to each web app
     individually.  Uncomment the following entry if you would like
     a user to be authenticated the first time they encounter a
     resource protected by a security constraint, and then have that
     user identity maintained across *all* web applications contained
     in this virtual host.
-->
<!--
 <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->

Beginner Issues

Lets get some newbie stuff out of the way first. These are the source of most problems:

  • Using localhost as the server name. This is, by far, the most misleading aspect of CAS system documentation in various wiki cross-references below.  A red flag should go up anytime you see localhost. Use the actual computer (server) name. Use of raw IP addresses is discouraged for some reason. On a Windows XP box, the actual computer name can be determined by clicking "Start", select "Settings", "Control Panel", "Performance & Maintenance", "System" and then finally clicking on the "Computer Name" tab. The "Full Computer Name" is your server name. Use it instead of localhost (or you will die...guaranteed). For purposes of this wiki, lets call the first computer with the CAS server "compA" and the other one, simply, "compB".
  • Being totally clueless about SSL. CAS is designed so you can hack SSL out of it. But any self-respecting multi-domain SSO architecture needs to feature SSL because it is fundamentally important that client applications are not "spoofed" into thinking that the user has already logged in. For this, we need trust established between server and client. To implement this, we use a technique that is analogous to most daily business transactions. When you cash a check or use a credit card, you are sometimes asked to show your driver license to prove you are who you say you are. In this case, the merchant and you trust the Department of Motor Vehicles (DMV), which is a mutually acceptable independent "Certification Authority". Browsers establish (and enforce) this type of mutually trustworthy transactions through the Secure Socket Layer (SSL) protocol. Besides digital signatures certifying servers and clients, SSL gives you data confidentiality through encryption and data integrity through message-specific codes...good stuff.   For applications that are deployed within your development environments, you can "self-certify" this SSL transaction. However, for external facing applications, when you are in production dealing with third parties outside your own LAN, SSL must be certified by a trusted third party like Verisign, Thawte. Any more discussion of SSL is out of scope of this wiki...which will just get your demo going. But please plan to do the needed research into SSL and also ensure that your company can support this down the road (it costs a few hundred dollars per certificate, plus you need to be legit business with a real address, etc...).
  • Ambiguous JRE location. You may have installed Java SDK and Tomcat long, long time ago and are unclear which JRE your tomcat points to anymore, for instance. Or you may already be doing SSL. It is VERY critical you know your tomcat installation (crystal clear).  Otherwise, the best course of action is to install from scratch for this demo. Proceed at your own risk if you are unclear on this and skip the JDK and tomcat installs in the wiki below. CAS is going to be a critical server component in your enterprise anyway. It needs a little more respect than a completely self-contained JSP application that is plopped into an existing tomcat as a war. CAS clients will have that luxury, but certainly not the server, which brings me to...
  • What is SOA? I don't think you need to be an Service Oriented Architecture (SOA) expert to use CAS but you need to have some idea. Just installing a CAS sever is not going to get the job done. You need to configure your client application somewhat to "consume" the SSO service provided by the CAS server. This is basic to distributed, loosely coupled interactions that characterize SOA. For a tomcat client application of CAS, this just means two things: add a few lines to your web.xml, drop a few jars files into your WEB-INF/lib and reload.  It does not get any simpler than that.  But it does mean that your old login page, if you had one, perhaps will be retired. Even more deeply, it means that your applications will have less information about the user than before.  If you didn't care ok.  But note that you will need to ensure that any roles, privileges etc are taken care of within client application. To mix authentication and authorization is a bad idea.  CAS is designed to serve many masters and cannot possibly please everyone with the same exact code. I will show you below how you can get the SSO login username within a tomcat client application, but that it is all you will get out-of-the-box from CAS.  Your client application will have to take it from there, as to what the user can or cannot see.  And, remember that CAS does not do a tomcat login for you...it is within CAS that a user session is maintained, not the local application container. Even if you did not comprehend all that SOA, you may proceed for now, but I guarantee you will run up against that wall: "How do I know who logged in?". Answer: CAS supports that out-of-the-box. "How do I control what the user can see?". Answer: CAS does not (should not) do that for you...it just lets user in the door (or slams it shut). Your application will not (and should not) have even the password of the user to "play with". Client applications need to independently figure out roles/privileges.  Unless, of course, you are willing to create rather exotic extensions or configurations of CAS itself. Note that CAS is open source, so you can certainly do whatever you want with it to suit your needs.  Or, you may look into frameworks that play well with CAS like Acegi Security. But we are getting way ahead of ourselves.

With that preamble, let's begin...

Stepwise Instructions

Basically, recommend starting fresh with (instruction are for a Windows XP professional computers) with no JDK/JRE or Tomcat. Using Microsoft Internet Explorer Version 7.0.5730.11 to verify SSL etc.

Step 1: Install JDK Version

  • download jdk-1_5_0_11-windows-i586-p.exe from java.sun.com
  • conduct a typical installation, doing next, next, next
  • set JAVA_HOME system environment variable to, well, java home... C:\Program Files\Java\jdk1.5.0_11

Step 2: Used keytool to self-author a server certificate for DEMO

Reference: http://www.ja-sig.org/wiki/display/CAS/Solving+SSL+issues

Entire Command Prompt Dialog is below...

Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\Documents and Settings\ukari>cd \program*
The filename, directory name, or volume label syntax is incorrect.

C:\Program Files>cd java

C:\Program Files\Java>cd jdk*

C:\Program Files\Java\jdk1.5.0_11>cd bin

C:\Program Files\Java\jdk1.5.0_11\bin>keytool -genkey -alias tomcat -keypass changeit -keyalg RSA
Enter keystore password:  changeit
What is your first and last name?
  [Unknown]:  compA
What is the name of your organizational unit?
  [Unknown]:  Information Systems
What is the name of your organization?
  [Unknown]:  Pacific Disaster Center
What is the name of your City or Locality?
  [Unknown]:  Kihei
What is the name of your State or Province?
  [Unknown]:  HI
What is the two-letter country code for this unit?
  [Unknown]:  US
Is CN=compA, OU=Information Systems, O=Pacific Disaster Center, L=Kihei, ST=HI, C=US correct?
  [no]:  yes

C:\Program Files\Java\jdk1.5.0_11\bin>keytool -export -alias tomcat -keypass changeit -file server.crt
Enter keystore password: changeit
Certificate stored in file <server.crt>

C:\Program Files\Java\jdk1.5.0_11\bin>keytool -import -file server.crt -keypass changeit -keystore ..\jre\lib\security\cacerts
Enter keystore password: changeit
Owner: CN=compA, OU=Information Systems, O=Pacific Disaster Center, L=Kihei, ST=HI, C=US
Issuer: CN=compA, OU=Information Systems, O=Pacific Disaster Center, L=Kihei, ST=HI, C=US
Serial number: 462030d8
Valid from: Fri Apr 13 15:39:36 HST 2007 until: Thu Jul 12 15:39:36 HST 2007
Certificate fingerprints:
MD5: CC:3B:FB:FB:AE:12:AD:FB:3E:D 5:98:CB:2E:3B:0A:AD
SHA1: A1:16:80:68:39:C7:58:EA:2F:48:59:AA:1D:73:5F:56:78:CE:A4:CE
Trust this certificate? [no]: yes
Certificate was added to keystore

C:\Program Files\Java\jdk1.5.0_11\bin>

CAS Server Name

In the above dialog, it is critical that you enter the CAS server name as the answer (compA) to the following question:

What is your first and last name?
[Unknown]: compA

At this stage we have a .keystore file created in C:\Documents and Settings\<user> and the %JAVA_HOME%\jre\lib\security\cacerts file with the corresponding certificate.

Step 3: Install Tomcat

  • Selected the Windows Installer version at http://tomcat.apache.org/download-55.cgi#5.5.23
  • When prompted for directory, changed it to C:\tomcat5.5.23
  • When prompted for applications, I added all examples, webapps etc. (useful for testing CAS)
  • When prompted for JRE, changed default to %JAVA_HOME%/jre (IMPORTANT: this should be home of new cacerts from step 2)
  • Clicked finish and verified tomcat running as a service and also by doing http://localhost:8080
  • Ensure that logs look very clean as well.
  • Set environment variable %CATALINA_HOME% as C:\tomcat5.5.23

    Tomcat JRE Home

    In the above dialog, it is critical in that you select the JRE home where you put the cacerts file in step 2. In this case, please note that we are using %JAVA_HOME%/jre

Step 4: Configure Tomcat server.xml

  • uncomment connector element for port 8443 (SSL)
  • add the parameters for keystoreFile, keystorePass, truststoreFile as shown below
  • bounce tomcat
<!-- Define a SSL HTTP/1.1 Connector on port 8443 -->
<Connector port="8443" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="C:/Documents and Settings/ukari/.keystore"
keystorePass="changeit"
truststoreFile="C:/Program Files/Java/jdk1.5.0_11/jre/lib/security/cacerts" />

You could need to add the following parameters to the Connector definition in order to activate the SSL on Tomcat:

SSLEnabled="true" protocol="org.apache.coyote.http11.Http11NioProtocol" / protocol="org.apache.coyote.http11.Http11Protocol"

Step 5: CASify HelloWorld Servlet

http://www.ja-sig.org/wiki/display/CASC/Using+CASFilter
[http://www.ja-sig.org/products/cas/client/javaclient/index.html]

<filter>
<filter-name>CAS Filter</filter-name>
<filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
<param-value>https://compA:8443/cas/login</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
<param-value>https://compA:8443/cas/serviceValidate</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
<param-value>compA:8080</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/servlet/HelloWorldExample</url-pattern>
</filter-mapping>

Step 6: Drop CAS Client jar into the servlets-examples context

URL: http://www.ibiblio.org/maven/cas/jars/

Step 7: Download and Deploy CAS

URL: http://www.ja-sig.org/products/cas/downloads/index.html

  • download the cas-server-3.0.7.zip file.
  • extract it all to c:\cas-server-3.0.7 directory.
  • copy cas.war from C:\cas-server-3.0.7\cas-server-3.0.7\target to C:\Tomcat5.5.23\webapps
    (this deploys cas if tomcat is running...but just to be sure...step 8)

Step 8. Clean start

  • stop tomcat, clear all logs, start tomcat
  • examine logs

stdout_20070413.log (looks "normal"):

2007-04-13 16:32:02,082 INFO [org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler] \- <No PasswordEncoder set. Using default: org.jasig.cas.authentication.handler.PlainTextPasswordEncoder>
2007-04-13 16:32:02,082 INFO [org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler] \- <No Class to Support set. Using default: org.jasig.cas.authentication.principal.UsernamePasswordCredentials>
2007-04-13 16:32:02,082 WARN [org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler] \- <org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler is only to be used in a testing environment. NEVER enable this in a production environment.>
2007-04-13 16:32:02,129 INFO [org.jasig.cas.ticket.proxy.support.Cas20ProxyHandler] \- <No UniqueTicketIdGenerator specified for org.jasig.cas.ticket.proxy.support.Cas20ProxyHandler. Using org.jasig.cas.util.DefaultUniqueTicketIdGenerator>
2007-04-13 16:32:04,316 INFO [org.jasig.cas.web.ServiceValidateController] \- <No successView specified. Using default of casServiceSuccessView>
2007-04-13 16:32:04,316 INFO [org.jasig.cas.web.ServiceValidateController] \- <No failureView specified. Using default of casServiceFailureView>
2007-04-13 16:32:04,363 INFO [org.jasig.cas.web.ServiceValidateController] \- <No authentication specification class set. Defaulting to org.jasig.cas.validation.Cas20ProtocolValidationSpecification>
2007-04-13 16:32:04,363 INFO [org.jasig.cas.web.ServiceValidateController] \- <No successView specified. Using default of casServiceSuccessView>
2007-04-13 16:32:04,363 INFO [org.jasig.cas.web.ServiceValidateController] \- <No failureView specified. Using default of casServiceFailureView>
2007-04-13 16:32:04,473 INFO [org.jasig.cas.web.flow.AuthenticationViaFormAction] \- <FormObjectClass not set. Using default class of org.jasig.cas.authentication.principal.UsernamePasswordCredentials with formObjectName credentials and validator org.jasig.cas.validation.UsernamePasswordCredentialsValidator.>
2007-04-13 16:32:22,223 INFO [org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner] \- <Starting cleaning of expired tickets from ticket registry at [Fri Apr 13 16:32:22 HST 2007]>
2007-04-13 16:32:22,223 INFO [org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner] \- <0 found to be removed. Removing now.>
2007-04-13 16:32:22,223 INFO [org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner] \- <Finished cleaning of expired tickets from ticket registry at [Fri Apr 13 16:32:22 HST 2007]>

catalina.2007-04-13.log (looks "normal"):

Apr 13, 2007 4:31:56 PM org.apache.catalina.core.AprLifecycleListener lifecycleEvent
INFO: The Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:\Tomcat5.5.23\bin;.;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\Program Files\ATI Technologies\ATI Control Panel;C:\Program Files\Common Files\Adobe\AGL
Apr 13, 2007 4:31:57 PM org.apache.coyote.http11.Http11BaseProtocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
Apr 13, 2007 4:31:58 PM org.apache.coyote.http11.Http11BaseProtocol init
INFO: Initializing Coyote HTTP/1.1 on http-8443
Apr 13, 2007 4:31:58 PM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 2859 ms
Apr 13, 2007 4:31:58 PM org.apache.catalina.core.StandardService start
INFO: Starting service Catalina
Apr 13, 2007 4:31:58 PM org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/5.5.23
Apr 13, 2007 4:31:58 PM org.apache.catalina.core.StandardHost start
INFO: XML validation disabled
Apr 13, 2007 4:32:00 PM org.apache.catalina.startup.HostConfig deployWAR
INFO: Deploying web application archive cas.war
Apr 13, 2007 4:32:06 PM org.apache.coyote.http11.Http11BaseProtocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
Apr 13, 2007 4:32:07 PM org.apache.coyote.http11.Http11BaseProtocol start
INFO: Starting Coyote HTTP/1.1 on http-8443
Apr 13, 2007 4:32:07 PM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
Apr 13, 2007 4:32:07 PM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/47 config=null
Apr 13, 2007 4:32:07 PM org.apache.catalina.storeconfig.StoreLoader load
INFO: Find registry server-registry.xml at classpath resource
Apr 13, 2007 4:32:07 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 8891 ms

localhost.2007-04-13.log (looks "normal"):

Apr 13, 2007 4:32:04 PM org.apache.catalina.core.ApplicationContext log
INFO: org.apache.webapp.balancer.BalancerFilter: init(): ruleChain: \[org.apache.webapp.balancer.RuleChain: [org.apache.webapp.balancer.rules.URLStringMatchRule: Target string: News / Redirect URL: http://www.cnn.com], [org.apache.webapp.balancer.rules.RequestParameterRule: Target param name: paramName / Target param value: paramValue / Redirect URL: http://www.yahoo.com], [org.apache.webapp.balancer.rules.AcceptEverythingRule: Redirect URL: http://jakarta.apache.org]\]
Apr 13, 2007 4:32:05 PM org.apache.catalina.core.ApplicationContext log
INFO: ContextListener: contextInitialized()
Apr 13, 2007 4:32:05 PM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: contextInitialized()
Apr 13, 2007 4:32:05 PM org.apache.catalina.core.ApplicationContext log
INFO: ContextListener: contextInitialized()
Apr 13, 2007 4:32:05 PM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: contextInitialized()

All other log files stderr, admin, host-manager, jakarta_service, manager are of size 0K. Checked stderr just to make sure...

Step 9. Try It

Logs

stdout_20070413.log (three new records):

2007-04-13 16:39:01,238 INFO [org.jasig.cas.web.flow.AutomaticCookiePathSetterAction] \- <Setting ContextPath for cookies to: /cas>
2007-04-13 16:39:18,271 INFO [org.jasig.cas.authentication.AuthenticationManagerImpl] \- <AuthenticationHandler: org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler successfully authenticated the user which provided the following credentials: uday>
2007-04-13 16:39:18,286 INFO [org.jasig.cas.CentralAuthenticationServiceImpl] \- <Granted service ticket [ST-2-V03EdNba1e3cMxeoEbEHwXfoefftIeeRxuO-20] for service [http://compA:8080/servlets-examples/servlet/HelloWorldExample] for user [uday]>

localhost.2007-04-13.log (new records):

Apr 13, 2007 4:38:51 PM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: sessionCreated('6ED4CFE9A5D7BA592372A3E67DF7D6E8')
Apr 13, 2007 4:38:51 PM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: attributeAdded('6ED4CFE9A5D7BA592372A3E67DF7D6E8', 'edu.yale.its.tp.cas.client.filter.didGateway', 'true')
Apr 13, 2007 4:38:51 PM org.apache.catalina.core.ApplicationContext log
INFO: InvokerFilter(ApplicationFilterConfig[name=Path Mapped Filter, filterClass=filters.ExampleFilter]): 31 milliseconds
Apr 13, 2007 4:39:19 PM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: attributeAdded('6ED4CFE9A5D7BA592372A3E67DF7D6E8', 'edu.yale.its.tp.cas.client.filter.user', 'uday')
Apr 13, 2007 4:39:19 PM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: attributeAdded('6ED4CFE9A5D7BA592372A3E67DF7D6E8', 'edu.yale.its.tp.cas.client.filter.receipt', '\[edu.yale.its.tp.cas.client.CASReceipt userName=[uday] casValidateUrl=[https://compA:8443/cas/serviceValidate] proxyCallbackUrl=[null] pgtIou=[null] casValidateUrl=[https://compA:8443/cas/serviceValidate] proxyList=\[\[\]\]\]')
Apr 13, 2007 4:39:19 PM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: attributeRemoved('6ED4CFE9A5D7BA592372A3E67DF7D6E8', 'edu.yale.its.tp.cas.client.filter.didGateway', 'true')
Apr 13, 2007 4:39:19 PM org.apache.catalina.core.ApplicationContext log
INFO: InvokerFilter(ApplicationFilterConfig[name=Path Mapped Filter, filterClass=filters.ExampleFilter]): 860 milliseconds

All other logs unchanged. Done (Success). Well sorta. This is the absolute minimal "proof-of-life" with Yale CAs. Still need to CAS-ify some other application and see if it works between two applications on separate machines...which leads us to...

Step 10: Setup up distinct domain

Repeat repeat Steps 1 thru 4 above for compB. Just do it, dont worry about creating a server certificate etc...Tomcat seems to like a keystore. So just do it.

Step 11: CAS-ify client on second domain slightly differently

  • In step 5, modify the direction above to add compB as the client-host as follows (obviously, leave the rest of it alone).

    Server Name Specification

    <init-param>
     <param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
     <param-value>compB:8080</param-value>
    </init-param>
    
  • Though not needed for this demo, I promised to show you how you may get the user. This is how. Add the following right after the server name specification as a child of the <filter> element and as a sibling of the above init-param element for server name. This entry will allow you to access the login user name as String username = request.getRemoteUser();within any of the secured jsp. Try it!

    Config for getting user name in request

    <init-param>
      <param-name>edu.yale.its.tp.cas.client.filter.wrapRequest</param-name>
      <param-value>true</param-value>
     </init-param>
    
  • Another useful tip is that you may secure as many resources as you wish within your context by simply adding the following:

    Adding multiple secured resources

    <filter-mapping>
        <filter-name>CAS Filter</filter-name>
        <url-pattern>/servlet/RequestHeaderExample</url-pattern>
      </filter-mapping>
    

The final compB web.xml modifications should look as follows:

<filter>
 <filter-name>CAS Filter</filter-name>
 <filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>
  <init-param>
   <param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
   <param-value>https://compA:8443/cas/login</param-value>
  </init-param>
  <init-param>
   <param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
   <param-value>https://compA:8443/cas/serviceValidate</param-value>
  </init-param>
  <init-param>
   <param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
   <param-value>compB:8080</param-value>
  </init-param>
  <init-param>
   <param-name>edu.yale.its.tp.cas.client.filter.wrapRequest</param-name>
   <param-value>true</param-value>
  </init-param>
</filter>

<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/servlet/HelloWorldExample</url-pattern>
</filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/servlet/RequestHeaderExample</url-pattern>
</filter-mapping>

Step 12: Drop CAS Client jar into the servlets-examples context

Identical to Step 6, but for compB.

Step 13: Establish trust with CAS SSO Server

Ref: http://blogs.sun.com/andreas/entry/no_more_unable_to_find

Here is the most elegant way to do it, I think.

  • In compB, simply copy the cacerts to cacerts.old (to save it just in case)
  • run java InstallCert compA:8443 (i.e. provide the argument "compA:8443" to the executable "InstallCert")
    (adapted source code for InstallCert.java from Sun blog by Andreas Sterbenz is shown below)
  • Answer 1 to the prompt

This will add compA into trust store of compB (while neatly obviating the problem discussed in the Sun blog referenced above).

InstallCert.java - adapted from Sun Microsystems blog by Andreas Sterbenz
/*
 * @(#)InstallCert.java 1.1 06/10/09
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms.
 */


import java.io.*;
import java.net.URL;

import java.security.*;
import java.security.cert.*;

import javax.net.ssl.*;

public class InstallCert {

    public static void main(String[] args) throws Exception {
    String host;
    int port;
    char[] passphrase;
    if ((args.length == 1) || (args.length == 2)) {
        String[] c = args[0].split(":");
        host = c[0];
        port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
        String p = (args.length == 1) ? "changeit" : args[1];
        passphrase = p.toCharArray();
    } else {
        System.out.println("Usage: java InstallCert <host>[:port] [passphrase]");
        return;
    }

    File file = new File("jssecacerts");
    if (file.isFile() == false) {
        char SEP = File.separatorChar;
        File dir = new File(System.getProperty("java.home") + SEP
            + "lib" + SEP + "security");
        file = new File(dir, "jssecacerts");
        if (file.isFile() == false) {
        file = new File(dir, "cacerts");
        }
    }
    System.out.println("Loading KeyStore " + file + "...");
    InputStream in = new FileInputStream(file);
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
    ks.load(in, passphrase);
    in.close();

    SSLContext context = SSLContext.getInstance("TLS");
    TrustManagerFactory tmf =
        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(ks);
    X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
    SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
    context.init(null, new TrustManager[] {tm}, null);
    SSLSocketFactory factory = context.getSocketFactory();

    System.out.println("Opening connection to " + host + ":" + port + "...");
    SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
    socket.setSoTimeout(10000);
    try {
        System.out.println("Starting SSL handshake...");
        socket.startHandshake();
        socket.close();
        System.out.println();
        System.out.println("No errors, certificate is already trusted");
    } catch (SSLException e) {
        System.out.println();
        e.printStackTrace(System.out);
    }

    X509Certificate[] chain = tm.chain;
    if (chain == null) {
        System.out.println("Could not obtain server certificate chain");
        return;
    }

    BufferedReader reader =
        new BufferedReader(new InputStreamReader(System.in));

    System.out.println();
    System.out.println("Server sent " + chain.length + " certificate(s):");
    System.out.println();
    MessageDigest sha1 = MessageDigest.getInstance("SHA1");
    MessageDigest md5 = MessageDigest.getInstance("MD5");
    for (int i = 0; i < chain.length; i++) {
        X509Certificate cert = chain[i];
        System.out.println
            (" " + (i + 1) + " Subject " + cert.getSubjectDN());
        System.out.println("   Issuer  " + cert.getIssuerDN());
        sha1.update(cert.getEncoded());
        System.out.println("   sha1    " + toHexString(sha1.digest()));
        md5.update(cert.getEncoded());
        System.out.println("   md5     " + toHexString(md5.digest()));
        System.out.println();
    }

    System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
    String line = reader.readLine().trim();
    int k;
    try {
        k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
    } catch (NumberFormatException e) {
        System.out.println("KeyStore not changed");
        return;
    }

    X509Certificate cert = chain[k];
    String alias = host + "-" + (k + 1);
    ks.setCertificateEntry(alias, cert);

    OutputStream out = new FileOutputStream(file);
    ks.store(out, passphrase);
    out.close();

    System.out.println();
    System.out.println(cert);
    System.out.println();
    System.out.println
        ("Added certificate to keystore 'cacerts' using alias '"
        + alias + "'");
    }

    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

    private static String toHexString(byte[] bytes) {
    StringBuilder sb = new StringBuilder(bytes.length * 3);
    for (int b : bytes) {
        b &= 0xff;
        sb.append(HEXDIGITS[b >> 4]);
        sb.append(HEXDIGITS[b & 15]);
        sb.append(' ');
    }
    return sb.toString();
    }

    private static class SavingTrustManager implements X509TrustManager {

    private final X509TrustManager tm;
    private X509Certificate[] chain;

    SavingTrustManager(X509TrustManager tm) {
        this.tm = tm;
    }

    public X509Certificate[] getAcceptedIssuers() {
        throw new UnsupportedOperationException();
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
        throw new UnsupportedOperationException();
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
        this.chain = chain;
        tm.checkServerTrusted(chain, authType);
    }
    }

}

Step 14: Test

  • Clean logs, restart tomcat on compB.
  • Repeat Step 9.
  • Change compA to compB on the URL.
  • You should not be challenged to login (QED)
  • Restart tomcat and try again, you should have to login to compB
  • And, not to compA

Congratulations.