Friday, September 5, 2008

Single Sign-On with SAML and ColdFusion

My implementation of SAML is largely based on this post by David Rutter which is unfortunately riddled with errors that I spent more time than I would like to admit working through. His post and some of my background knowledge are from Phil Duba's Saml and ColdFusion Series which was very useful until about halfway through part 5 where it ventures out of my comfort zone into compiling java code for use within CF.

What I have done is merge the two approaches into a single solution usable on CF8(and possibly others although it has not been tested) for connecting to PingIdentity's PingFederate Service Provider server such as that used by Rearden Commerce.

PreReqs

  1. Download the binary(bin) Apache XML Security Library. I used the most current version 1.4.2.

  2. Unzip it and copy from xml-security-1_4_2\libs serializer.jar and xmlsec-1.4.2.jar into ColdFusion8\lib and restart the CF service

  3. Buy or Generate an x509 certificate and provide the public portion to you service provider. I will cover this in more depth in another post.


Now we are ready to get into the code.

We start with SAML Assertion XML and fill in the dynamic portions: ID's, dates and username.












#username#

urn:oasis:names:tc:SAML:1.0:cm:bearer









Now we will Sign our XML Assertion:


//injest the xml
samlAssertionElement = samlAssertionXML.getDocumentElement();
samlAssertionDocument = samlAssertionElement.GetOwnerDocument();
samlAssertion = samlAssertionDocument.getFirstChild();

//create the neccesary Java Objects
SignatureSpecNS = CreateObject("Java", "org.apache.xml.security.utils.Constants").SignatureSpecNS;
TransformsClass = CreateObject("Java","org.apache.xml.security.transforms.Transforms");
SecInit = CreateObject("Java", "org.apache.xml.security.Init").Init().init();
XMLSignatureClass = CreateObject("Java", "org.apache.xml.security.signature.XMLSignature");

//set up the signature
sigType = XMLSignatureClass.ALGO_ID_SIGNATURE_RSA_SHA1;
signature = XMLSignatureClass.init(samlAssertionDocument, javacast("string",""), sigType);
samlAssertionElement.insertBefore(signature.getElement(),samlAssertion.getFirstChild());

//set up signature transforms
TransformsClass = CreateObject("Java","org.apache.xml.security.transforms.Transforms");
transformEnvStr = TransformsClass.TRANSFORM_ENVELOPED_SIGNATURE;
transformOmitCommentsStr = TransformsClass.TRANSFORM_C14N_EXCL_OMIT_COMMENTS;
transforms = TransformsClass.init(samlAssertionDocument);
transforms.addTransform(transformEnvStr);
transforms.addTransform(transformOmitCommentsStr);

KeyStoreClass = CreateObject("Java" , "java.security.KeyStore");
//injest your previously created keystore
ksfile = CreateObject("Java", "java.io.File").init("c:\temp.keystore");
inputStream = CreateObject("Java", "java.io.FileInputStream").init(ksfile);
ks = KeyStoreClass.getInstance("JKS");
ks.load(inputStream,"SamlTest");
keypw = "mypass";
key = ks.getKey("SamlTest",keypw.toCharArray());
cert = ks.getCertificate("SamlTest");
publickey = cert.getPublicKey();

signature.addDocument("", transforms);

//optionally include the cert and public key
//signature.addKeyInfo(variables.cert);
//signature.addKeyInfo(variables.publickey);

signature.sign(key);

samlAssertionXML = toBase64(toString(samlAssertionXML), "utf-8");



and then we use a form to post it to the service provider









And there you have homegrown SAML Single Sign-on Solution In ColdFusion.

Here is the source file in it's entirety because blogger has a tendency to mangle code

Please feel free to post questions or comments.