X.509 Certificates

New CAS documentation site

CAS documentation has moved over to apereo.github.io/cas, starting with CAS version 4.x. The wiki will no longer be maintained. For the most recent version of the documentation, please refer to the aforementioned link.

X.509 Certificates Authentication Handler

X.509 Certificates are a very technical subject. Many people know something about them, but it is difficult for any two people to hold a discussion without some misunderstanding about common terms. This is not the place for a general discussion of PKI or Java Cryptography, although this subject is based on both topics. There are a few key decisions in the configuration, and they need to be explained in plain language. The decisions also have to be placed in the context of various institutional authentication strategies.

Planning

An institution may establish a Public Key Infrastructure to issue User certificates that can be installed in a Web Browser. The simplest example is the Certificate Services built into Microsoft Active Directory, but more flexible solutions supporting all browsers and operating systems exist in Java programs such as EJBCA. A certificate issued by an institution will have within it some field that can be translated to the locally preferred Principal name, while a certificate issued by some generic public service probably only has an E-Mail address in it. This document assumes that your institution already has PKI in place and has already issued certificates to some users who have installed them in some browsers. Instructions on any of these steps is outside the scope of what can be explained here.

A driver's license is issued by your State. It contains information about a person (name, address, age) and information that may be used to associate the license with the individual it describes (picture, signature). A certificate is issued by a Certificate Authority (CA). It contains information about an account (name, department, E-Mail) and information that may be used to prove that someone who claims to be is actually the owner of the certificate (a Public Key matching another Private Key that the owner has kept secret). However, while there are only 50 states issuing drivers licenses, any organization can set up a Certificate Authority. How does one make the decision to trust certificates issued by a CA.

In the old days, it was imagined that there would be some central administrative unit that everyone trusted that could assert which CA's were reliable and which were not. In most cases that turned out to be impractical and not cost effective. So the common practice is for each company or university to create its own CA and then manually distribute a certificate identifying the Public Key of that CA to every computer, user, browser, or server that the institution services. That is fine for in house use (intranet) but when a server also provides information to outsiders (applicants, customers, suppliers, ...) then it is necessary to purchase a Server Certificate from one of the public commercial CA's (notably VeriSign or Thawte).
A recent evolution is the existence of national government CA's that are being used for certificates in electronic ID cards. If you belong to a country that has implemented a nation-wide electronic ID card that contains the right X.509 certificate, then you're lucky. You should check whether you can use those eID's for your purpose (example: http://eid.belgium.be/). Usually this solves quite a lot of issues that are explained in the next paragraphs.

Certificates are exchanged as part of the SSL (also called TLS) initialization that occurs when any Browser connects to an https: Web site. A certain number of public CA certficates are preinstalled in each Browser by Microsoft, Mozilla, or whoever else makes the Browser. Basically the same set of certificates is installed by Microsoft in every copy of Windows and by Sun in every copy of Java. However, no application, system, or language comes with any certificate that you've created inside your company or campus as a Certificate Authority (CA).

There are two problems. We began by assuming that your organization is already able to generate and distribute certificates that a User can install in Windows (for IE) or in Firefox or some other browser, and that somewhere in that certificate there is a field that contains the Principal name or can be easily mapped to the Principal name that you want CAS to use. The remaining problem is to make sure that the Browsers, Servers, and Java are all prepared to support these institutional certificates and, ideally, that these institutional certificates will be the only ones exchanged when a Browser connects to CAS.

When a Browser connects to CAS over an https: URL, the Server identifies itself by sending its own certificate. For CAS to make any sense at all, the Browser must already have installed a certificate identifying and trusting the CA that issued the CAS Server certificate. If the Browser is not already prepared to trust the CAS server, then an error message pops up saying the server is not trusted, and Users should never, ever believe that that error message is OK. You certainly do not want users sending their passwords to any machine on the network that claims to be CAS. Unless you can preinstall an institutional CA certificate in every user's browser before they every visit the CAS site, then the CAS server must have a commercial certificate issued by VeriSign or Thawte for some amount of money every year.

After the Server sends the certificate that identifies itself, it then can then send a list of names of Certificate Authorities from which it is willing to accept certificates. Ideally, this list will include only one name, the name of the internal institutional CA that issues internal intranet-only certificates that internally contain a field with the CAS Principal name.

A User may install any number of certificates into the Browser from any number of CA's. If only one of these certficates comes from a CA named in the list of acceptable CA's sent by the Server, then most Browsers will automatically send that one certificate without asking, and IE can be configured in the Security tab to not ask when there is only one possible choice. This presents a User experience where CAS becomes transparent to the user after some initial setup, and the login happens automatically. However, if the Server hosting CAS sends more than one CA name in the list and that matches more than one certificate on the Browser, then the user will get prompted to choose a Certificate from the list. A user interaction defeats much of the purpose of Certificates in CAS.

CAS does not control this exchange. It is handled by the Web Server, which may be an Application Server. If you deploy CAS in a large J2EE server with lots of other applications, then you may not have the control to require the Application Sever to vend only one CA name when a Browser visits CAS. So if you want to use X.509 Certficates in CAS, you should consider this requirement when choosing the hosting environment. 

The ideal situtation is to select a Server that can identify itself with a public certficate issued by VeriSign, but then require the Client certficate only be issued by the internal corporate/campus authority.

When CAS gets control, a User certificate may have been presented by the Browser and be stored in the HttpServletRequest object. The CAS X.509 Authentication Handler examines that certficate and verifies that it was issued by the trusted institutional authority. The CAS X.509 Credentials to Principal Resolver then searches through the fields of the certificate to identify one or more fields that can be turned into the Principal indentifier that the applications expect (the same identifier that would have been generated by userid and password form identification).

While an institution can have one Certificate Authority that issues certificates to employees, clients, machines, services, and devices, it is more common for the institution to have a single "root" Certificate Authority that in its entire existence only issues a handful of certificates. Each of these certificates identifies a secondary Certificate Authority that issues a particular category of certificates (to students, staff, servers, etc.). It is possible to configure CAS to trust the root Authority and, implicitly, all the secondary authorities that it creates. This, however, makes CAS only as secure as the least reliable secondary Certificate Authority created by the institution. At some point in the future, some manager will buy a product that requires a new class of certificates. He will ask to create a Certificate Authority that vends these certificates to the machines running this new product. He will then turn administration of this mess over to a junior programmer or consultant. If CAS trusts any certificate issued by any Authority created by the root, it will trust a fradulent certificate forged by someone who has acquired control of what was intended to be a special purpose, isolated CA. Therefore, it is better to configure CAS to only accept certificates from the one secondary CA  specifically expected to issue credentials to individuals, instead of trusting the institutional root CA.

Configuring Servers

CAS can only operate securely when the client connects over TLS/SSL. So with or without X.509 certificate support, a CAS installation will already have confronted the problem of obtaining a certificate for the Server, installing it, and turning on the SSL port. However, as described above, when you then go to add X.509 support to a CAS previously driven by the userid/password form, you may want to use a different certificate and SSL configuration, which may involve adding a second port.

Configuring Tomcat

Anything said here extends the Apache reference for SSL under Tomcat found at http://tomcat.apache.org/tomcat-5.5-doc/ssl-howto.html (this is the 5.5 version, but the SSL configuration is the same on all Tomcat releases).

The Tomcat server is configured in TOMCAT_HOME/conf/server.xml with one or more <Connector> elements. Each of these elements defines one port number on which Tomcat will listen for requests. Connectors that support SSL are configured with one or two files that represent a collection of X.509 certificates.

The keystoreFile is a collection of X.509 certificates one of which Tomcat will use to identify itself to Browsers. This certificate contains the DNS name of the server on which Tomcat is running which the HTTP client will have used as the servername part of the URL. It is possible to use a file that contains multiple certificates (in which case Tomcat will use the certificate stored under the alias "Tomcat" or, if that is not found, will use the first certificate it finds that also has an associated private key). However, to assure that no mistakes are made it is sensible practice to use a file that has only the one host certificate, plus of course its private key and chain of parent Certificate Authorities.

The truststoreFile is a collection of X.509 certificates representing Certificate Authorities from which Tomcat is willing to accept User certificates. Since the keystoreFile contains the CA that issued the certificate identifying the server, the truststoreFile and keystoreFile could be the same in a CAS configuration where the URL (actually the port) that uses X.509 authentication is not the well know widely recognized URL for interactive (userid/password form) login, and therefore the only CA that it trusts is the institutional internal CA.

However, the recommended strategy if you are planning to support both X.509 and userid/password validation through the same port is to put a public (VeriSign, Thawte) certificate for this server in the keystoreFile, but then put only the institutional internal CA certificate in the truststoreFile. Logically and in all the documentation, the Certificate Authority that issues the certificate to the Server which the Browser trusts is completely and logically independent of the Certificate Authority that issues the certificate to the User which the Server then trusts. Java keeps them separate, Tomcat keeps them separate, and Browsers should not be confused if, during SSL negotation, the Server requests a User certificate from a CA other than the one that issued the Server's own identifying certificate. In this configuration, the Server issues a public certificate every browser will accept, and the Browser is strongly urged to send only a private institutional certificate that can be mapped to a Principal name.

If you previously configured CAS without X.509 authentication, then you probably have the keystoreFile already configured and loaded with a certificate identifying this server. All you need to add is the truststoreFile part.

The configured connector will look something like:

<!-- Define a SSL HTTP/1.1 Connector on port 443 -->
<Connector port="443" maxHttpHeaderSize="8192"
       maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
       enableLookups="false" disableUploadTimeout="true"
       acceptCount="100" scheme="https" secure="true"
       clientAuth="want" sslProtocol="TLS"
       keystoreFile="/path/to/keystore.jks" keystorePass="secret"
       truststoreFile="/path/to/myTrustStore.jks" truststorePass="secret" />
<!-- if you do not specify a truststoreFile, then the default java "cacerts" truststore will be used-->

 The clientAuth="want" tells Tomcat to request that the Browser provide a User certificate if one is available. If you want to force the use of User certificates, replace "want" with "true". If you specify "want" and the browser does not have a certificate, then the Webflow configuration described below will forward the request to the userid/password form.

Note: If you specify "true" here, then Tomcat will require the Browser present a User certificate that was issued by one of the Certificate Authorities registered in the truststoreFile collection. However, the X.509 AuthenticationHandler will be configured with additional constraints. A User certificate may be acceptable to Tomcat as configured here, but fail the additional tests of the AuthenticationHandler. The Webflow will normally then send the Browser the userid/password form, since normally an interactive login is the backup when a non-interactive login fails. So if you want a URL that accepts only X.509 authentication, it is not enough to simply change "want" to "true". You also have to change the Webflow so that the Browser is never forwarded to the interactive form.

The keystore can be in JKS (a collection of keys and certificates created by Sun for Java) or PKCS12 (a collection of keys and certificates based on public standards) format when using Tomcat. When using both PKCS12 and JKS keystore types then you should specify the type of each keystore by using the keystoreType and truststoreType attributes.

The JKS (Java Key Store) file format is maintained by a tool provided by Sun in the /bin subdirectory of the Java Development Kit (JDK) distribution of your current version of Java. It is documented in http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/keytool.html. Generally, you will import the certificate of the institutional Certificate Authority (the one that issues User certificates) using the command:

keytool --import -alias myAlias \
   -keystore myTrustStore.jks \
   -file certificateForInstitutionalCA.crt

Then myTrustStore.jks would become the value of the truststoreFile parameter of the Tomcat Connection configuration element.

Java cacerts

The previous discussion explains how Tomcat (and through it the build in Java support for SSL called JSSE) is configured to exchange Server and User certficates during SSL setup. However, once Tomcat (or any other Java Application Server) passes the certificate to CAS, that part of the configuration is now hidden. CAS uses the built in Java support for certificates to validate and inspect what it has been passed. That, in turn, depends on the native Java support for Certificate Authorities.

In the Java Runtime Environment under which CAS will run, there is a JRE_HOME/lib/security/cacerts file. Although it does not end in .jks, it is a JKS file preconfigured with the certificates of VeriSign, Thawte, and the other public Certificate Authorities. If you have an institutional Certificate Authority issuing User certificates, and if (as with most institutions) you have not decided out of the goodness of your own heart to give VeriSign a ton of money to get a Certificate Authority certificate from them, then you should also use keytool to import the institutional CA certificate into the cacerts collection of the JRE under which you intend to run CAS.

Add issuers to the truststore

You will need to use a special keystore called a "trustore" that contains the certificates of the root CA's that you want to trust in handing out client certificates. It is most secure to create a new, blank keystore and add only the root CA's that you really trust. However you may also choose the easy way and also trust all the root CA's that are already part of the default truststore of the JVM (cacerts). You must be a priveleged user to access the cacerts truststore of the JVM. Every keystore is protected by a password. The default password of the default "cacerts" file is "changeit".

note: "\" means that the command should continue on the same line.

To import a CA certificate into the trusted list of the JVM:

keytool --import -alias myAlias \
    -keystore $JAVA_HOME/jre/lib/security/cacerts \
    -file filenameForYourTrustedCA.crt

The import should succeed (answer yes to confirm the import). Now, you may use all certificates issued by this issuer for certificate based login in e.g. Tomcat.

Alternatively, here are the commands to create a blank keystore and import trusted issuers into that:

keytool -genkey -keyalg RSA -alias "selfsigned" -keystore myTrustStore.jks
 -storepass "secret" -validity 360

keytool -delete -alias "sensible-name-for-ca" -keystore myTrustStore.jks

keytool --import -alias myAlias \
   -keystore myTrustStore.jks \
   -file filenameForYourTrustedCA.crt

Configuring CAS for X.509 User Certificates 

Including the Handler

In the pom.xml file for your CAS Maven2 WAR Overlay, add the following dependency:

<dependency>
     <groupId>org.jasig.cas</groupId>
     <artifactId>cas-server-support-x509</artifactId>
     <version>${cas.version}</version>
</dependency>

Configuring Web Flow

The original webflow XML configuration file is located in ${project.home}/cas-server-webapp/src/main/webapp/WEB-INF/login-webflow.xml. You can obtain the copy for the version of CAS that you are using by retrieving it directly from CAS' revision control at github. For example, for version 3.4.10, you can download <https://raw.github.com/Jasig/cas/v3.4.10/cas-server-webapp/src/main/webapp/WEB-INF/login-webflow.xml>.

Webflow consists of a sequence of operations. Some operations are tests that yield a true or false result. Other operations yield named results like "success" or "error". Each result causes the flow to move to another operation.

The operation that displays the Form with the userid and password fields is the "viewLoginForm". There are two ways to get to viewLoginForm. You can fall through to it from the sequence of preliminary tests (to see if you already have a login cookie, etc.), or you can come back to it if you enter a bad userid or password. The configuration will be changed to add X.509 handling to the sequence of preliminary tests.

Note: SPNEGO is another optional test. If you intend to support both SPNEGO and X.509 certificates, you will have to make a decision in advance as to which of the two goes first. In concrete terms, if a user is sitting on a Windows machine logged in as "hyde", but the X.509 certficate in the Browser indicates his identity is "jekyll", then which of the two credentials will CAS look at and accept first. Then adjust the Webflow configuration accordingly.

First remove the XML comments (the "<!-"  at the start and the "->" at the end) from around the "startAuthenticate" check.

<!--
<action-state id="startAuthenticate">
<action bean="x509Check" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="viewLoginForm" />
</action-state>
-->

In version 3.4.2 and later, change <action bean="x509Check" /> to <evaluate expression="x509Check" />. Failure to do so will result in the message "CAS Server unavailable" when trying to access the login page.

Although the name of the state is "startAuthenticate", you can see from the source that this is an "x509Check". SPNEGO also uses the name "startAuthenticate" for its test, so if you use both SPNEGO and X509, then one of the the two will be "startAuthenticate" and the other will be something else, like "continueAuthenticate". In that case, the transition on="error" of the first (startAuthenticate) will be changed from "viewLoginForm" to the id of the second, "continueAuthenticate".

Version-specific warning

The following section applies to version 3.4.2. In later versions, you will want to replace "generateLoginTicket" with "startAuthenticate" in the "renewRequestCheck" and "gatewayRequestCheck" tests.

Now change the outcome of the previous test. It now reads

<if test="${externalContext.requestParameterMap['renew'] != ''
   &amp;&amp; externalContext.requestParameterMap['renew'] != null}"
   then="viewLoginForm"
   else="generateServiceTicket" />

Change the outcome of the test from "viewLoginForm" to "startAuthenticate", so that when the test is true it jumps to the test you just uncommented above.  Now go back a few tests to the gatewayRequestCheck and do almost the same thing:

<if test="${externalContext.requestParameterMap['gateway'] != ''
   &amp;&amp; externalContext.requestParameterMap['gateway'] != null
   &amp;&amp; flowScope.service != null}"
   then="redirect"
   else="viewLoginForm" />

The one difference is that in this test, the "viewLoginForm" that you change to "startAuthenticate" is in the else part of the test.

Now that the WebFlow itself has been configured, the "x509Check" bean mentioned above has to be defined. That is done in the  /WEB-INF/cas-servlet.xml file.  Go to that file and look for a commented bean with id="x509Check". The correct uncommented version of the bean definition is:

<bean
   id="x509Check"
   p:centralAuthenticationService-ref="centralAuthenticationService"
   class="org.jasig.cas.adaptors.x509.web.flow.X509CertificateCredentialsNonInteractiveAction" >
   <property name="centralAuthenticationService" ref="centralAuthenticationService"/>
</bean>

If this matches what is inside the comments, then simply remove the comments. If it does not, then replace the commented bean with this code.

Configuring the Authentication Handler

One bean must be added to the list of Authentication Handlers. In file WEB-INF/deployerConfigContext find the <bean id="authenticationManager">. Inside it, there is a <property name="authenticationHandlers">.  In it is a <list>. To this list, add:

<bean
   class="org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler">
         <property name="trustedIssuerDnPattern" value="CN=Hogwarts Certifying Authority.+" />
         <!--
         <property name="maxPathLength" value="3" />
         <property name="checkKeyUsage" value="true" />
         <property name="requireKeyUsage" value="true" />
         -->
</bean>

Of course, you will replace the regular expression in the value parameter of the trustedIssuerDnPattern with a pattern that matches the name of the Certificate Authority that will issue the User certficates you are prepared to trust. This should only include institutional CA's that you expect to issue User certificates that might be installed in a Browser for CAS authentication. The Browser presents the User certificate and the certficate of the CA that issued the User Certficate and the certificate of each successive issuing CA back to whatever CA acts as the root. If any Distinguished Name in this chain matches the trustedIssuerDnPattern regular expression, then the User certificate is acceptable.

This is separate from the truststoreFile check of the User certificate chain performed by Tomcat (or the comparable check made by any other Application Server that hosts CAS). It is also separate from the cacerts check of the User certificate chain performed by Java. All of these checks have to match, and in addition one of the CA's in the chain has to have a DN that matches the trustedIssuerDnPattern.

You should not put too much faith in the parameters of X509CredentialsAuthenticationHandler all by themselves. Remember that if anyone has a rogue CA that will be authenticated by your truststoreFile and cacerts, then they can use it to create a secondary CA with any DN they want, and then use it to forge User certificates. However, if CAS has to share an Application Server with other code that requires that other (non User) institutional CA's be configured to the Server and JVM, then this is a way to tighten CAS down to the one intended CA. 

Note that you can use a regex for defining a series of trusted issuers. It is highly recommended to list the root CA as the trusted issuer, although it is possible to list any issuer in the chain for the client certificate to be accepted. The certificate chain is transversed from root to end-user certificate while these checks are being performed

  • Is this certificate still valid?
  • Is this the certificate of a trusted issuer? [default=.*]
  • Is the pathLength within the configured limits? [default=1]
    • Checks either the number of intermediate certificates
    • OR if it is and end-user certificate:
      (optional) Does the certificate contain the right KeyUsage constraint?
      • checkKeyUsage: check the value of the key usage extension for client certificate if it is present in the end-user cert [default=false]
      • requireKeyUsage: fail is the key usage extension is not in the end-user certificate [default=false]
  • Is there a trusted issuer in the chain?

Keep the WEB-INF/deployerConfigContext file open for the next step.

Configuring the Credentials to Principal Resolver

One bean must be added to the list of Credential to Principal Resolvers in the WEB-INF/deployerConfigContext file. In the "authenticationManger" find the property name="credentialsToPrincipalResolvers" which has a <list>. To this list you add a bean. However, exactly what bean you use depends on your strategy for resolving the X.509 certificate fields into the same type of Principal name that would have been produced by an interactive userid/password login.

  • org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentialsToDistinguishedNamePrincipalResolver - Retrieve the complete distinguished name and use that as the identifier.
  • org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentialsToIdentifierPrincipalResolver - Transform some subset of the identifier into the ID for the principal.
  • org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentialsToSerialNumberPrincipalResolver - Use the unique serial number of the certificate.
  • org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentialsToSerialNumberAndIssuerDNPrincipalResolver - Create a most-likely globally unique reference to this certificate as a DN-like entry, using the CA name and the unique serial number of the certificate for that CA.

Start with a simple example that takes the first CN field in the Distinguished Name and uses it as the Principal:

<bean
   class="org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentialsToIdentifierPrincipalResolver">
   <property name="identifier" value="$OU $CN" />
</bean>

This bean creates a principal based on the pattern in the value attribute of the "identifier" property. The value has variables formed by "$" followed by the name of a field in the DN. So as it scans a certificate with a Distinguished Name of "CN=Potter, OU=Gryffindor, DC=hogwarts, DC=edu" it will match the CN= to the $CN of the identifer, and the OU= to the $OU of the pattern, producing a Principal name of "Gryffindor Potter".

A field can be extracted from the Certificate and then resolved to a Principal name through an LDAP lookup (included from CAS 3.1). This is a considerably more complicated example, see the LDAP documentation for more details:

<property name="credentialsToPrincipalResolvers">
[...]
			<bean
				class="org.jasig.cas.authentication.principal.CredentialsToLDAPAttributePrincipalResolver" >
				<!-- the LDAP resolver uses any kind of CredentialsResolver that will first extract a value from the request.
					The extracted value will then be matched against an LDAP attribute as configured below -->
				<property name="credentialsToPrincipalResolver">
					<!--
						This resolver this will create a unique ID of form:
						SERIALNUMBER=<certificate_serial_number>, <certificate_issuerDN>
						eg: SERIALNUMBER=25263647932548882251489395556682941778, SERIALNUMBER=200301, CN=Citizen CA, C=BE
						["SERIALNUMBER=" and ", " can be configured, see included source]
						This should be stored and compared in LDAP using a record with schema description:
						 *  EQUALITY distinguishedNameMatch
						 *  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12   [=distinguishedName]
					-->
					<bean class="org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentialsToSerialNumberAndIssuerDNPrincipalResolver" />
				</property>
				<!-- attribute that needs to be matched to in LDAP: -->
				<property name="filter" value="eIDnumber=%u" />
				<!-- to be retrieved from LDAP as the new principal (logged in user) for CAS: -->
				<property name="principalAttributeName" value="uid" />
				<property name="searchBase" value="ou=people,dc=domain,dc=be" />
				<!-- reference to LDAP server configuration: -->
				<property name="contextSource" ref="LdapContextSource" />
			</bean>
[...]
		</list>
	</property>
[...]
	</bean>
	<!-- for LDAP lookups; check CAS LDAP documentation for more details -->
	<bean id="LdapContextSource" class="org.jasig.cas.adaptors.ldap.util.AuthenticatedLdapContextSource">
		<property name="pooled" value="true" />
		<!-- remove userName and password if you can do anonymous lookup of the necessary values -->
		<property name="userName" value="CN=root" />
		<property name="password" value="secret" />
		<property name="urls">
			<list>
				<value>ldap://localhost/</value>
			</list>
		</property>
	</bean>
 </beans>