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.
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:
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.
The token message takes the following form:
The message, we’ll call it “M”, is as follows:
USERID:
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.
When implementing this system we create a java class called SSOUtil that can both create and evaluate tokens.
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.
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
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):
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):
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 requirements must be satisfied to support this system. These are explained below.
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.
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).
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.
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.
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
Re-enter new password:
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
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.
As a thought experiment, I muse what could be done to make a cluster to run locally.
How to Set up Google Analytics on localhost for testing in development.
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.
Insert bio here