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.
Now, let’s discuss what’s required in an SSO token. A valid token requires the following properties:
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.
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):
A safe transmission of the token requires the following properties:
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).
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: