Simple Single Sign-On

It is possible to create a single sign-on system using only the Java jdk and its built-in support for public key infrastructure (PKI) and digital signatures..

Object Partners

The Problem

Over the years, I’ve seen a number of situations where Single Sign-On is necessary between applications, but no Commercial Off The Shelf (COTS) solution is available. This is often the case, because the COTS solutions are large, expensive, and require a significant amount of maintenance. Selecting and implementing a COTS solution is usually a company-wide decision and is not undertaken as part of some other, smaller project. However, this doesn’t lessen the frequent need for solid SSO solutions.

Many people try to follow standards of some type (SAML, etc) when implementing SSO, but this often leads right back to the problem of implementing a COTS product locally, not to mention that all SSO partners are required to support the same protocol and version. When dealing with external partners syncing up on SSO protocols and versions can be dicey at best.

To solve this problem, I’ve used a solution I call Simple Single Sign-On that uses a lightweight mechanism to create a cryptographically solid token that can be passed from one entity and validated by another. The system requires nothing more than the standard JDK on both sides as a prerequisite.

First, we’ll discuss what the SSO token must look like. We’ll then cover how a system can be built to create and evaluate those tokens. Lastly, we’ll talk about a few more necessary concepts for supporting the system.

Note: Attached to this post is a zip containing an implementation of the core of the system with the two necessary java keystores. The class itself, SSOUtil contains a main method that demonstrates using the creation and evaluation of the tokens. Download the source and keystores here.

The Token

Token Requirements

Now, let’s discuss what’s required in an SSO token. A valid token requires the following properties:

  • Non-repudiation: the receiver can validate from where/whom a message originates.
  • Message integrity: the receiver can validate that the message has not changed.

Both of the above characteristics are absolute requirements of tokens so that the asserting application can identify a user and then pass that identity on to the receiving application. It’s critical that the receiving party can be sure that it really is the asserting application “vouching” for the user listed in the token.

Enter Digital Signatures

The two properties discussed above can be provided by a digital signature. Digital signatures are made possible by public key cryptography. The key concept of public key cryptography is that the key used to encrypt a message is different than the key used to decrypt a message. This is where public-private key pairs come into play. The private key can encrypt a message and then the public key can decrypt the message and vice-versa.

Just encrypting a message with the private key can form a primitive digital signature. However, there are some security problems associated with just encrypting the message with a private key (i.e. forgery is in the realm of possibility). To address this issue, the message is first run through a cryptographic hash function and then the hash value is encrypted by the private key. The receiving application can then take the same hash of the plaintext and compare it to the decrypted (with the asserter’s public key) hash from the signature. If the two match, then the signature is verified. Note: to validate the signature, the receiving application must be able to access the plaintext message. The image from http://en.wikipedia.org/wiki/Digital_signature below describes this process well (click on the image to enlarge):

Digital Signature Operation

Token Transmission

A safe transmission of the token requires the following properties:

  • Message confidentiality: only the sender and receiver can read the message. No external third-party can recover the message.

It is important to keep the token a secret so that third-parties are unable to intercept the token in transit and use it to impersonate the user. If the transmission is not occurring behind a firewall, the best and most common way to achieve this is to transmit the token over an SSL connection (from a defense-in-depth perspective, it’s a good idea to transmit tokens over SSL even behind a firewall).

Token Uniqueness

There’s one more characteristic of a token that is important. The encrypted/signed token should not result in the same string every time for a particular user. If the token is the same every time, then the risk of the token being stolen becomes much, much higher as an attacker only has to steal the token once to be able to log in as the user until the theft is discovered.

To avoid this problem, we need to add in a timestamp to the message so that the encrypted part of the token is different every time. This accomplishes two goals:

  1. The created tokens are different every time
  2. Tokens can expire by having existed for more than a set amount of time.

SSO System Definition

Now that we have established that digital signatures and a secure channel give us the properties we need, we can define our SSO system. First, we’ll define the tokens. We’ll then discuss how the token generation and validation can be integrated into applications.

Token Definition

The token message takes the following form:

The message, we’ll call it “M”, is as follows:

USERID:;TIMESTAMP:;ALIAS:<assertingparty_alias>;

Then, the token is as follows: M;DIGSIG:<signatureof_M>

While USER_ID and TIMESTAMP are somewhat self-explanitory, ALIAS needs more explanation. Certificates and their (if appropriate) private keys are stored in Java keystores and accessed by a key called an alias. So the alias is used to lookup the appropriate public and private keys. The alias is important when the receiver is trying to validate a token. Upon receipt of a token the receiver looks up the certificate (which contains the public key) associated with the alias listed in the token. It can then use the public key to validate the incoming token. Creating/initializing these keystores is described at the end of this post.

SSOUtil

When implementing this system we create a java class called SSOUtil that can both create and evaluate tokens.

Initializing SSOUtil

Asserting applications need to initialize the SSOUtil so that it’s possible to create tokens. Receiving applications only need to be able to evaluate tokens, unless they are, at some point, to become asserters themselves. This means that asserting applications need to know their own alias so that they can retrieve the private key necessary to create signatures. The receiving applications only need to have access to the public key(s) associated with any aliases that may send in authenticating tokens.

Asserting Application Operation

When one application needs to propagate the identity of a user to another application it becomes what we’ve called the “asserting application” to this point. The asserting application must create a map of key-value pairs that are necessary for the receiving application to identify the user. Sending just a user id is usually sufficient, but other parameters like organization id or company id can be passed along as well. This nice thing about this system is that any parameters may be passed from one system to another by including them in the message parameters map.

The parameters map is then passed to the SSOUtil’s createToken method. The createToken method builds the token message by cycling through the passed in message parameter key-value pairs and building up a string. It then adds the timestamp and alias. After creating that message, the createToken() method digitally signs the message and appends a parameter (DIGSIG) with the hexadecimally encoded signature string to the end of the message. The message plus the the DIGSIG parameter and value forms the token as described above. The createToken() method can be seen below:

public String createToken(Map<String, String> messageParameters){

String returnString ="";

if(messageParameters == null){

return**null**;

}

String token = null;

try{

Signature sig = Signature.getInstance(“SHA1withDSA”);

sig.initSign(privateKey);

Iterator keyIterator = messageParameters.keySet().iterator();

while(keyIterator.hasNext()){

String currentKey = keyIterator.next();

String currentValue = messageParameters.get(currentKey);

if(!"".equals(returnString)){

returnString = returnString + ”;“;

}

returnString = returnString + currentKey+”:“+currentValue;

}

long currentTime = System.currentTimeMillis();

returnString = returnString + “;TIMESTAMP:“+currentTime;

returnString = returnString + “;ALIAS:“+asserterAlias;

sig.update(returnString.getBytes());

byte[] sigBytes = sig.sign();

String tokenSignature = convertToHex(sigBytes);

returnString = returnString + “;DIG_SIG:“+tokenSignature;

}

catch(NoSuchAlgorithmException nsae){

nsae.printStackTrace();

}

catch(InvalidKeyException ike){

ike.printStackTrace();

}

catch(SignatureException se){

se.printStackTrace();

}

return returnString;

}

The conceptual operation of the above method is as follows (click on the image to enlarge): Asserter Tasks

Receiving Application Operation

The receiving application receives a token from the asserting application and evaluates it to determine it’s authenticity. The receiving application calls the SSOUtil’s validateToken() method which first parses the token to obtain the encoded signature and the alias of the asserting application. The validateToken method then retrieves the public key registered in its local keystore for the alias specified in the token. The encoded signature is decoded into a byte array as required for validation. Both the byte array and the public key are passed to a Java Signature object which validates the signature.

After receiving confirmation that the token is valid, the receiving application calls the SSOUtil.retrieveValidatedTokens() to get the key value pairs passed in the token. At least one of which should be able to identify a user within the receiving application. NOTE: the retrieveValidatedTokens() method does call validateToken() so calling both within the receiving application is redundant with respect to determining token validity. So, it’s up to the implementor if they want to explicitly call both or just the retrieveValidatedTokens() method which will just return a null map if the token doesn’t validate. Here’s the validateToken() method:

public**boolean** validateToken(String token){

int digSigIndex = token.indexOf(“;DIG_SIG:”);

int tokenIndex = digSigIndex+9;

int endMessageIndex = digSigIndex;

int aliasIndex = token.indexOf(“;ALIAS:”)+7;

int aliasEndIndex = token.indexOf(”;”, aliasIndex);

boolean returnBool = false;

String aliasString = token.substring(aliasIndex, aliasEndIndex);

String signatureString = token.substring(tokenIndex);

String messageString = token.substring(0, endMessageIndex);

byte[] sigBytes = convertToBytes(signatureString);

try{

Signature sig = Signature.getInstance(“SHA1withDSA”);

PublicKey asserterKey = keystore.getCertificate(aliasString).getPublicKey();

sig.initVerify(asserterKey);

sig.update(messageString.getBytes());

returnBool = sig.verify(sigBytes);

}

catch(NoSuchAlgorithmException nsae){

nsae.printStackTrace();

}

catch(InvalidKeyException ike){

ike.printStackTrace();

}

catch(SignatureException se){

se.printStackTrace();

}

catch(KeyStoreException kse){

kse.printStackTrace();

}

return returnBool;

} The conceptual operation of the method is as follows (click on the image to enlarge): Receiver Tasks

Integration of SSOUtil Into Applications

The most common ways we’ve integrated the SSOUtil into the asserting applications is either to use some sort of streaming servlet/portlet or to add the token when a link is clicked that leads to the receiving application. In either case, the token must be submitted when making the first request for a protected resource on the receiving application. This can be done using any mechanism where the token string can be retrieved by the receiving application.

On the receiving application side, often application servers have some mechanism for creating custom identity asserters. If these are available, it’s usually pretty simple to evaluate the tokens using the SSOUtil within them. If there is no identity assertion capability built-in, then we’ve found it useful to place servlet filters into the receiving applications that can validate and then set up the user’s identity.

More Public Key Concepts

More public key requirements must be satisfied to support this system. These are explained below.

Certificate Chains of Trust

To support the creation of digital signatures, you need access to a public-private key pair. The public key is stored and accessed via an x.509 certificate. Java’s facility for this is the keystore mechanism. Java provides a tool called keytool that enables the manipulation of keystores. Using the keytool, we have a couple of options for the creation of certificates – generate a CSR and have it signed by a certificate authority or generate a self-signed certificate.

Before discussing the two options, it’s important to understand that certificates suffer from a transmission vulnerability. If an attacker is in the middle of the transmission it’s possible to intercept the certificate, record it and send a false certificate on to the eventual recipient, thus allowing the attacker to intercept future messages and change them, sending them on with a signature that is validated by the false certificate.

Generating and Signing a CSR

When creating the public-private key pair, there is a choice to make. Like normal SSL certificate generation, it is possible to generated a certificate signing request (CSR). The CSR can then be sent to a certificate authority which signs it with it’s own private key and returns it. To allow the system to work, the “certificate chain of trust” must be stored within the key tool. This means that the CA’s certificate (also called the root certificate) must be in the keystore as well as the signed CSR (which is now a certificate).

Using a CSR and a certificate authority signature is the most robust solution and can be used to alleviate the transmission vulnerability. If both parties intended to be part of the SSO communication have access to a certificate authorities certificate (acquired out-of-band), then the asserting party’s certificate can be sent to the receiving party in-band without fear of forgery (since it can be validated via the certificate authority’s signature).

Creating a Self-Signed Certificate

Using the keytool, we can create a self-signed certificate which instead of being signed by a certificate authority is signed by it’s own private key. This option is simpler to implement but means that the certificate should not be transmitted in-band, since no independent authority is validating it (therefore making it vulnerable to interception and forgery).

Despite these drawbacks, for many applications using a self-signed certificate is sufficient. Especially in situations where all applications participating in SSO are in-house (a very frequent case), so the transmission problem is minimized. However, caution must be taken to minimize the number of people with access to the production private key (stored in the keystore) to prevent against internal attacks.

Keystore Creation

In creating our system we start by creating an asserting keystore and a receiving keystore. The asserting keystore is created with java keytool by using the -genkeypair option and then specifying the keystore file name using the -keystore option. The public key for the key pair is then exported to a text file. Once the public key file is created, it can be added to the receiver keystore using the -importcert command.

Creating the Asserter Keystore

To create the asserter keystore for this example the command sequence looks like the following:

keytool -genkeypair -alias asserter -keysize 1024 -keystore sso_asserter.jks

Enter keystore password:

Re-enter new password:

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

What is the name of your organizational unit? [Unknown]: Demo Org

What is the name of your organization? [Unknown]: Demo, Inc.

What is the name of your City or Locality? [Unknown]: Minneapolis

What is the name of your State or Province? [Unknown]: Minnesota

What is the two-letter country code for this unit? [Unknown]: US

Is CN=SSO Asserter, OU=Demo Org, O=“Demo, Inc.”, L=Minneapolis, ST=Minnesota, C=US correct?

Enter key password for (RETURN if same as keystore password):

Re-enter new password:

Exporting the Asserter Public Key

After creating the asserter public-private key pair, the public key needs to be exported so it can be included in receiving application keystores.  The following command sequence shows this:

keytool -exportcert -alias asserter -keystore ./sso_asserter.jks -file asserterCert.txt

Creating the Receiver Keystore

The receiver keystore is created by exporting the public key from the asserter keystore and importing it into a new or prexisting keystore. If the keystore does not exist it is created as part of the -importcert command and the user is asked to create a password for the keystore. The command sequence below shows the -importcert command in action:

$ keytool -importcert -alias asserter -keystore ./sso_receiver.jks -file asserterCert.txt

Enter keystore password:

Re-enter new password:

Owner: CN=SSO Asserter, OU=Demo Org, O=“Demo, Inc.”, L=Minneapolis, ST=Minnesota, C=US

Issuer: CN=SSO Asserter, OU=Demo Org, O=“Demo, Inc.”, L=Minneapolis, ST=Minnesota, C=US

Serial number: 4e7a27be

Valid from: Wed Sep 21 13:06:54 CDT 2011 until: Tue Dec 20 12:06:54 CST 2011

Certificate fingerprints:

MD5: 05:32:CE:EA:73:50:23:71:A6:7B:8E:B6:EF:D0:64:B6

SHA1: 65:57:FE:DC:B2:7A:00:C2:06:20:4C:46:FC:06:D9:88:0D:80:75:4D

Signature algorithm name: SHA1withDSA

Version: 3

Trust this certificate? no: yes

Certificate was added to keystore

Note: The -importcert command can also be used on existing keystores to register public keys of other asserters.

Share this Post

Related Blog Posts

Unknown

How Much Linux Cluster for $6000?

September 27th, 2011

As a thought experiment, I muse what could be done to make a cluster to run locally.

Object Partners
Unknown

Setting Up Google Analytics on localhost

May 26th, 2011

How to Set up Google Analytics on localhost for testing in development.

Object Partners
Unknown

Helpful git-svn cheatsheet

April 28th, 2011

In my previous post A successful git-svn workflow, I wrote about starting out with git-svn. This post is a followup with notes on more advanced topics.

Object Partners

About the author