Home Contact

PD Versus-inspired Logophilduba.com

Adventures in Web Application Develompent by Phil Duba

Recent Entries

Popular Entries

Top Commenters

  • Nathan Mische (12)
  • CFFusionDev (6)
  • CFdevfusion (6)
  • Peter Bell (4)
  • Sean Corfield (3)
  • Rey Bango (3)
  • Terrence Ryan (3)
  • ah7866 (3)
  • Scott (2)
  • Jim Priest (2)

Slideshows

Dresser/Changing Table...
Images related to the lay...
Nursery renovations...
Pool Surprises...

Sponsored Links

Text Link Ads

SAML and ColdFusion Part 6 : Validating an Assertion

Posted On May 10, 2007 7:32 AM By Phil in SAML,ColdFusion

Welcome to what I believe will be the last entry in my SAML and ColdFusion series. This entry concentrates on validating a SAML assertion which includes checking for conditions, validating the signature, and extracting the assertion attributes. This post builds off of all the previous posts, so if you are just coming to this series at this point in time, I would suggest reading each of the previous posts in this series to see the background information. In the last post, we created a way to sign a document and create a keystore with a self-signed certificate. In this entry, we'll validate the assertion that was created, creating both a ColdFusion template for validation and a Java class file for verification.

At the end of the last post, the string returned from the Java class file included the digital signature. According to the SAML specification, when using the HTTP Binding, the assertion is to be submitted as a form variable using a Base64 encoding. The code for this would be rather simple:

<form method="post" action="https://thirdparty.services.com/samlConsumer" name="samlSSO">
<cfoutput>
<input type="hidden" name="SAMLResponse" value="#ToBase64(token,"utf-8")#"/>
</cfoutput>
</form>
</script language="JavaScript">
function submitSSO(){
document.samlSSO.submit();
}
setTimeout("submitSSO()",100);
</script>

Now, decoding and validating the SAML Assertion is relatively straightforward, from a ColdFusion perspective:

<cfif StructKeyExists(form,"samlResponse") AND Len(form.samlResponse) GT 0>
<!-- This assumes ColdFusion MX 7 -->
<cfset token = CharsetEncode(BinaryDecode(form.samlResponse,"Base64"),"utf-8")>
<cfset objSamlTest = CreateObject("java","path.to.SamlTest").init() />
<cfset ksString = "#GetTempDirectory()#temp.keystore">
<cfif objSamlTest.VerifySignature(token,ksString)>
   <!--
Peform the actions for other validations such as checking the NotAfter or NotBefore conditions
as well as any items you need to retrieve from the Attributes area.
-->

<cfset conditions = XmlSearch(tokenXml,"//*[namespace-uri() != '' and local-name()='Conditions']")> <cfif ArrayLen(conditions) EQ 0>
<cfthrow type="saml" message="No Conditions exist for the submitted SAML Assertion.">
</cfif>
<!-- This part will vary depending on the conditions expected to receive -->
<!--
DateConvertISO8601 can be found at http://www.cflib.org and was written
by David Satz (david_satz@hyperion.com). It can be downloaded at
http://www.cflib.org.
-->

<cfset condDate = DateConvertISO8601(conditions[1].XmlAttributes.Before, 0)>
<cfif DateCompare(DateConvert("local2Utc",now()),condDate) LTE 0>
   <cfthrow type="saml" message="NotBefore condition is invalid. Assertion validation failed.">
</cfif>

<cfset condDate = DateConvertISO8601(conditions[1].XmlAttributes.NotAfter, 0)>
<cfif DateCompare(DateConvert("local2Utc",now()),condDate) GTE 0>
   <cfthrow type="saml" message="NotAfter condition is invalid. Assertion validation failed.">
</cfif>

<cfelse>
<cfthrow type="saml" message="The SAML Assertion cannot be verified.">
</cfif>
<cfelse>
<cfthrow type="saml" message="The required form variable for SAML Authentication/Verification is not present.">
</cfif>

So that logic wasn't as straightforward as one might have thought, however, a lot of it will depend on the conditions you are expecting and the attributes being passed. In fact, on the project I worked on, I created a library of validation functions just so I could keep all of that logic in the same place for future reuse. Building off of the previous Java class, the VerifySignature function will perform almost the reverse type of logic to verify the signature. It still creates an XML document, but this time, it extracts the signature, loads the keystore and then validates the signature. Please note, there are many different ways to perform the validation. I have used a straight keystore lookup method, but it could be much more complicated if you are looking at doing a generic solution, such as X509 certificate lookup based on passed information within the signature. The simple Java function to validate the signature would look like:

public boolean VerifySignature(String token, String certPath) throws Exception {
//Initialize the library org.apache.xml.security.Init.init();
   
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.newInstance();
dbf.setNamespaceAware(true);
dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE);
DocumentBuilder db = dbf.newDocumentBuilder();
db.setErrorHandler(new org.apache.xml.security.utils.IgnoreAllErrorHandler());
      
byte inputBytes[] = token.getBytes();
Document doc = db.parse(new ByteArrayInputStream(inputBytes));
   
Element sigElement = null;
NodeList nodes = doc.getElementsByTagNameNS(org.apache.xml.security.utils.Constants.SignatureSpecNS,"Signature");
String password = "mypass";
   
if(nodes.getLength() !=0 ){
// Found Nodes for Signature element sigElement = (Element)nodes.item(0);
XMLSignature signature = new XMLSignature(sigElement,"");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(new File(certPath)),password.toCharArray());
PublicKey pubkey = ks.getCertificate("SamlTest").getPublicKey();
return signature.checkSignatureValue(pubkey);
}
return false;
}

Again, just like signing, the Document object is created, converting the passed string into an XML Document. After that, a search for the Signature node is performed. If the Signature node is not present, then it will return false, otherwise it steps through loading the keystore and then one method call is all that is needed to verify the signature.

Now, I based all of these entries on someone working with SAML 2.0. A friend of mine asked me to take a look at SAML being passed to his application and the above methods were failing. In his case, head had two Digital Signatures within his document and he was working in SAML 1.1. Staring with SAML 2.0, Assertion and the other major nodes in the SAML schema have the ID attribute. Prior to 2.0, the ID attribute was really AssertionID or something similar for the node in question. I found this blog entry on how to add the appropriate attribute to act as an ID, otherwise, one may get an error along the lines of "The Reference for URI ... has no XMLSignatureInput". The entry deals with signing, but you can use the same type of logic for verification as well.

This, most likely, concludes the series on working with SAML and ColdFusion. I know there is a lot of information out there on working with SAML, but some of it is overly complicated or applies only to specific libraries or vendor solutions. I hope the series has laid the ground work and can be a resource for others trying to do similar work with SAML and ColdFusion.

Related Blog Entries

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)

Phil Duba's Gravatar I'm adding this comment into this blog post as the link in it is incorrect for the issue with AssertionID and SAML 1.1. In the VerifiySignature method, place the following code:

byte inputBytes[] = token.getBytes();
Document doc = db.parse( new ByteArrayInputStream(inputBytes));

// Set up required ID attribute
Element rootElement = doc.getDocumentElement ();
String uriRef = doc.getDocumentElement().getAttribute( "AssertionId");
Attr id = doc.getDocumentElement().getAttributeNode( "AssertionID" );
IdResolver.registerElementById(rootElement, id);

Element sigElement = null ;
NodeList nodes = doc.getElementsByTagNameNS(org.apache.xml.security.utils.Constants.SignatureSpecNS ,"Signature" );

Make sure you also import the right classes for the above code to work.
# By Phil Duba | 8/20/07 3:35 PM
CFdevfusion's Gravatar Hi, I am getting this error below when I added the code you mentioned in comment above.

The system has attempted to use an undefined value, which usually indicates a programming error, either in your code or some system code.

Null Pointers are another name for undefined values.

The error occurred in C:\Inetpub\wwwroot\Test\SamlResponse.cfm: line 15

13 :<cfset ksString = "C:\CFusionMX7\runtime\jre\bin\temp.keystore">
14 :    
15 : <cfif objSamlTest.VerifySignature(token,ksString)>

Exceptions section
13:10:46.046 - java.lang.NullPointerException - in C:\Inetpub\wwwroot\Test\SamlResponse.cfm : line 15

So I get this error on line where objSamlTest.VerifySignature(token,ksString) is called. Any ideas?

FYI, before I added code from your comment section to VerifySignature method, I was getting this error

Cannot create an ElementProxy from a null argument

The error occurred in C:\Inetpub\wwwroot\Test\SamlResponse.cfm: line 15

13 :    <cfset ksString = "C:\CFusionMX7\runtime\jre\bin\temp.keystore">
14 :    
15 : <cfif objSamlTest.VerifySignature(token,ksString)>

Exceptions section
12:16:11.011 - org.apache.xml.security.exceptions.XMLSecurityException - in C:\Inetpub\wwwroot\Test\SamlResponse.cfm : line 13
Cannot create an ElementProxy from a null argument

Any ideas?

Also at one moment I got message in the cf code via cfthrow type ="saml"

The required form variable for SAML Authentication/Verification is not present.

It happened only once and after that no luck.

I dont know if CF takes time to refresh java class or something because how can error change from one thing to another in short period of time.
# By CFdevfusion | 6/6/08 1:22 PM
CFdevfusion's Gravatar Hi Phil,

Just wanted to say that everything is working fine now. To be honest, I dont know how that <b>Null Pointers</b> error went away. I did try these steps though I am not sure if they had anything to do with solving the issue.

I deleted all temporary classes created by CF7 at this location C:\CFusionMX7\wwwroot\WEB-INF\cfclasses. Then restarted the CF Service. After some tries, I was getting same error but after a while everything changed and I got the desired result.

Thanks for the excellent tutorials you have on your site.
# By CFdevfusion | 6/9/08 12:00 PM
Phil Duba's Gravatar @CFDev: no problem and sorry I didn't get back to posting sooner. You do have to clear the cfclasses folder and restart cf, at least that's what I found and it looks like you did to.
# By Phil Duba | 6/9/08 9:32 PM
tedzo's Gravatar Hi Phil,
Nice article.
I am having a bit of trouble understanding the big picture. I hope you can help me-
From what I understand, there are 3 entities:
1. A source application, say, http://boing.com/HR
2. A destination application- http://boing.com/sales
3. Stuff required for SSO- http://boing.com/samlConsumer

- User is asked to login when he tries to go to http://boing.com/HR
- User browses the application.
- User is redirected to http://boing.com/sales during browsing.
- The Form kicks in and user is redirected to http://boing.com/samlConsumer
- /samlConsumer decodes the samlResponse.

And then what? How does the user get to his final destination (http://boing.com/sales)?

I am obviously missing something here...

Thanks.
ted
# By tedzo | 6/19/08 8:54 PM
Phil Duba's Gravatar tedzo, i think it depends on your situation. if you are getting data, or authenticating the user onto that system to get their respective data, then you really do it as part of the overall authentication process. In my paradigm, they had a link from one system to go to another system so it was part of the redirect, and the other system actually sent a URL to use as part of their verification response. Not sure if that helps, but it really depends on your paradigm.
# By Phil Duba | 6/21/08 3:15 PM
CFFusionDev's Gravatar Hi tedzo,

Here is my take on your scenario

when /samlConsumer decodes the samlResponse, there are 2 options
a. userid or anyinfo that is sent in samlResponse, is validated against database and if validation is successful, user is redirected to final destination i.e. http://boing.com/sales (http://boing.com/sales)

b. validation was unsuccessful and you throw the user to the login portion of the http://boing.com/sales (http://boing.com/sales) site or you do any action as agreed upon with the IDP. These are the things usually agreed upon by IDP and SP.

HTH, All the best. Let us know how it went.
# By CFFusionDev | 7/28/08 10:30 AM
Satish Burnwal's Gravatar I am impressed with your article. However, I have a question - is there any document (standard doc or otherwise) that says that the URI attribute in Reference element must be same as the value of AssertionID ?
# By Satish Burnwal | 8/1/08 4:56 AM
Phil Duba's Gravatar If hou are using a 1.1 implementation, yes and I believe the SAML 1.1 documentation states as such. For SAML 2.0, it is an ID attribute instead of AssertionID, but again, it must be the same. I'm pretty sure the spec (I know, boring read) outlines this requirement.
# By Phil Duba | 8/1/08 11:45 AM
CFFusionDev's Gravatar Hi Phil,

I have a question and i believe you can help me. For Single sign on, there is a new case I have seen. The IDP is sending company id in AttributeValue element like this which is like your example in SAML post
<saml:Attribute Name="clientId">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; xsi:type="xs:string">07825</saml:AttributeValue></saml:Attribute>

But they are sending the employee id encrypted in another element like this
<saml:EncryptedAttribute>
   <xenc:EncryptedData Id="_67767" Type="http://www.w3.org/2001/04/xmlenc#Element"; xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">;
      <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"; xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>;
         <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">;
            <ds:RetrievalMethod Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey"...; URI="#_23232"/>
         </ds:KeyInfo>
         <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">;
            <xenc:CipherValue xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">;something
            </xenc:CipherValue>
         </xenc:CipherData>
   </xenc:EncryptedData>

   <xenc:EncryptedKey Id="_idhere" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">;
      <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"; xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>;
         <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">;
         <xenc:CipherValue xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">;cipher value here
         </xenc:CipherValue>
         </xenc:CipherData>
      <xenc:ReferenceList>
      <xenc:DataReference URI="#_uri here"/>
      </xenc:ReferenceList>
   </xenc:EncryptedKey>

</saml:EncryptedAttribute>xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>;<xenc:CipherData" target="_blank">http://www.w3.org/2001/04/xmlenc#"/>;<xe... xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">;<xenc:CipherValue xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">;certificate sent here</xenc:CipherValue></xenc:CipherData><xenc:ReferenceList><xenc:DataReference URI="#_uri here"/></xenc:ReferenceList></xenc:EncryptedKey>
</saml:EncryptedAttribute>

I have been asked to get employee id from this encryypted xml text.

Do you know or have a java program wherein the information can be decrypted and I can extract the employee id?

If you can suggest an algorithm, then that will be great also.

Thanks,
# By CFFusionDev | 8/15/08 10:40 AM
Tom Jones's Gravatar Thanks for the walk through, you did a really good job. I was wondering if by any chance you had a sample/example project you could post and share.

Thanks!
# By Tom Jones | 1/25/09 10:40 AM
Post Your Comments

Captcha

If you subscribe, any new posts to this thread will be sent to your email address.