Dragonsaber Website Design Banners Computer Art Print Design Kerwin"s Resume About Dragonsaber

April 7, 2010

ColdFusion / SAML (Part 3)

The Signature

This is where all of the pieces come together... the libraries & keystore from Part 1 and the XML from Part 2. Here's the script that applies all of the algorithms and transforms:


// 1) Injest the XML

samlAssertionElement = samlAssertionXML.getDocumentElement();

samlAssertionDocument = samlAssertionElement.GetOwnerDocument();

samlAssertion = samlAssertionDocument.getFirstChild();

// 2) Create 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"); // Access XML Signature CLASS

CanonicalizerClass = CreateObject("Java", "org.apache.xml.security.c14n.Canonicalizer"); // Access Canonicalizer CLASS

// 3) Signature

sigType = XMLSignatureClass.ALGO_ID_SIGNATURE_RSA_SHA1; // Define signature algorithm

canonType = CanonicalizerClass.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; // Define canonicalizer algorithm

signature = XMLSignatureClass.init(samlAssertionDocument, javacast("string",""), sigType, canonType); // Create signature and apply algorithms

// 4) Transforms

TransformsClass = CreateObject("Java","org.apache.xml.security.transforms.Transforms"); // Access Transforms CLASS

IncNSClass = CreateObject("Java","org.apache.xml.security.transforms.params.InclusiveNamespaces"); // Access Inclusive Namespaces CLASS

transformEnvStr = TransformsClass.TRANSFORM_ENVELOPED_SIGNATURE;

transformOmitCommentsStr = TransformsClass.TRANSFORM_C14N_EXCL_OMIT_COMMENTS;

transformIncNS = IncNSClass.init(samlAssertionDocument, "ds saml xs xsi");

transforms = TransformsClass.init(samlAssertionDocument); // Apply the base transform

transforms.addTransform(transformEnvStr); // Apply enveloped signature

transforms.addTransform(transformOmitCommentsStr); // Apply comment exclusion

transforms.item(1).getElement().appendChild(transformIncNS.getElement()); // Apply namespaces

// 5) Keystore

ksfile = CreateObject("Java", "java.io.File").init("#FULL_PATH_OF_KEYSTORE#");

inputStream = CreateObject("Java", "java.io.FileInputStream").init(ksfile);

KeyStoreClass = CreateObject("Java" , "java.security.KeyStore");

ks = KeyStoreClass.getInstance("JKS");


keypw = "#STOREPASS#";

key = ks.getKey("#KEYSTORE_ALIAS#",keypw.toCharArray()); // Must be the alias. Using the keystore filename returns an error. This is the private key.

cert = ks.getCertificate("#KEYSTORE_ALIAS#"); // Must be the alias. Using the keystore filename returns an error.

publickey = cert.getPublicKey();

// 6) Putting it Together

signature.addDocument("#ssouser.uuid#", transforms);

signature.addKeyInfo(variables.cert); // adds certificate to the signature

signature.addKeyInfo(variables.publickey); // ads public key to signature


samlAssertionElement.insertBefore(signature.getElement(),samlAssertion.getFirstChild()); // Takes samlAssertionElement and inserts the signature before the first node of saml:Assertion


1) The first part of this script is for injesting the SAML XML code. Then you're going to traverse the DOM to the location where the signature will be inserted. In this case, it will be the first child node of . This was the part I mentioned in Part 2 that had a CF/Java bug. When I had the full XML as a single variable, no matter what method I used to traverse the DOM (combinations of getFirstChild, getLastChild, getNextSibling, etc.), I couldn't get it to recognize the 2nd level... which is where the signature needs to be inserted (as a child of ). So by splitting it into the 3 variables, the middle variable is only one level. At first, I thought it was just a limitation of my Java skills, but I also had a Java expert look at the code and he also couldn't get out of the first level.

2) This portion is accessing various classes from the XML Security Libraries that were installed in Part 1.

3) Same as #2, but accessing signature related algorithms.

4) Same as #2, but accessing various transforms to apply.

5) This part is accessing the various parts of the keystore also created in Part 1.

6) The last part is adding more unique data to the signature. The unique user ID is thrown in along with the certificate and public key.

If you understood all of those steps... or even if you didn't, this should give you a sense of why SAML is super secure! Now I'm going to clean things up a bit:

<cfset samlAssertionXML = "#Replace(samlAssertionXML, "<?xml version=""1.0"" encoding=""UTF-8""?>", "", "all")#">
<cfset samlAssertionXML = "#Replace(samlAssertionXML, " xmlns:ds=""http://www.w3.org/2000/09/xmldsig##""", "", "all")#">
<cfset samlAssertionXML = "#Replace(samlAssertionXML, "<ds:Signature>", "<ds:Signature xmlns:ds=""http://www.w3.org/2000/09/xmldsig##"">", "all")#">
<cfset samlAssertionXML = "#responseOpen##samlAssertionXML##responseClose#">

1) The first line is removing the XML declaration because I have it hardcoded into the first piece of the XML already.

2) The next line is removing the namespace declaration because somewhere along the lines, in one of the Java methods, the declaration is getting added to every tag. You may not need to do this step, but I just implemented it because I wanted to get the XML as close as possible to my SP's XML spec.

3) The 3rd line just adds the namespace declaration back to <DS:SIGNATURE> tag.

4) The last line combines the 3 variables back together so that "samlAsertionXML" contains the entire XML document.

To get this ready for transport, the next step is to convert the XML to Base64...

<cfset samlAssertionXML = "#toBase64(toString(samlAssertionXML), "utf-8")#">

And finally, this can be sent over to the SP with an auto-submit form...

<form action="#SPService#" method="post">

<input type="hidden" name="SAMLResponse" value="#samlAssertionXML#" />

<input type="submit" value="Send to SP>



Jaazu said...

So playing the part of the SP, would I just pretty much do everything backwards? Do you have any articles that would help me out as I'm an SP trying to be a member of a IdP's federation to allow them access to my SaaS system.

Kerwin said...

Hello Jazzu-

I looked back at the references I used and the one guy (Phil Duba) that documented the SP portion doesn't have his URL anymore. It seems that most of us ColdFusion developers worked on the IDP side. But you're right. It's essentially the reverse of this. You have to work closely with the IDP to make sure you're decoding in the same way they are encoding. The SP side just receives the encoded piece, decodes and checks to see if it's a match. If it passes, then you simply allow the user in.


Panman said...

Has anyone used this code with Google Apps? Google "can't parse the response". Working with Google on it but just wanted to check here too.

P.S. I have ColdFusion code to decode the SAML Response from Google Apps. If anyone needs it let me know.

Ale said...

Hi Panman,

Please send me the CF code. I would like to a look at the CF code to decode Google Apps SAML. Thank You.

Panman said...

Hi Ale, I posted the code at CodeRanch. I did run into a random EOL inflation error that I never got back to but it might be related to my environment. And I never did get my SAML Response working with Google Apps, if you do please let me know! Thanks

Ryan said...

I'm curious why you say you couldn't get out of the first level.

I started with your posted code as a basis and went back to a single part xml. It worked fine for me (CF9).

Thanks for your post

Kerwin said...

@Ryan: Not sure why I was running into that problem, but I was using an unpatched CFMX7 at the time. CF9 ironed out a lot of bugs.

@All: Thanks for your comments on this series! I'm going to close further discussion on these 3 SAML items because they're really old now. Also, that was with an old position that I've moved on from.