Archive for May 12th, 2008

SAML, SSO and ColdFusion

Monday, May 12th, 2008

Overview

The following post is intended as an overview of how a clients IT department could integrate their intranet with our CMD system to provide a seamless single sign on. There are commercial systems that can do this and we have integrated with some of them. The following shows how to use the open source Apache XMLSecurity library to the same effect.

For an overview of SSO and SAML see my earlier posting. In this post I will illustrate an implementation using the SAML Post Profile, the Apache XML Security library and ColdFusion. (Since the Apache library is a java library, the JSP/java code is almost identical). There are two parts: the creation of the SAML on the intranet server (IdP) and the interpretation of this by an external service, or to use the correct terminology, service provider (SP).

Configuring the Idp

This article assumes that you have ColdFusion 8. Alternatively you could run Tomcat and use JSP although the syntax is different, the library calls are the same.

This implementation uses the Apache XML security library. Download and place in the WEB-INF/cfusion/lib directory for ColdFusion (for java into your applications lib directory), You probably already have many of the jar files and just need serializer.jar and xmlsec-nnn.jar, where nnn is the current version number.

Creating the SAML on the IdP

The starting point is a SAML assertion from the previous article. We need to add some real user data and time information. The users identity and other information to embed in the SAML would come from Active Directory, LDAP etc. For this example I’ve hard coded some values.

<cfscript>
    ssouser=StructNew();
    ssouser["email"]=”user@tagworldwide.com”;
    nowDateTime =
        #DateFormat(DateConvert(‘local2utc’,
          Now()),’YYYY-MM-DDT’)#
        & #TimeFormat(DateConvert(‘local2utc’,
          Now()),’HH:mm:SSZ’)#;
    nowDateTimePlus1 =
        #DateFormat(DateConvert(‘local2utc’,
          DateAdd(‘n’,1,Now())),’YYYY-MM-DDT’)#
        & #TimeFormat(DateConvert(‘local2utc’,
          DateAdd(‘n’,1,Now())),’HH:mm:SSZ’)#;
</cfscript>

The CFXML tag provides an easy way to populate the SAML

<cfoutput>
<cfxml variable=”samlAssertionXML”>
<samlp:Response
  xmlns:ds=http://www.w3.org/2000/09/xmldsig#chr(35)#
  xmlns:saml=”urn:oasis:names:tc:SAML:1.0:assertion” 
  xmlns:samlp=”urn:oasis:names:tc:SAML:1.0:protocol” 
  IssueInstant=”#nowDateTime#”
  MajorVersion=”1″ MinorVersion=”1″ 
  Recipient=”http://externalService/sso/post/
  ResponseID=”#CreateUUID()#”>
    <samlp:Status>
        <samlp:StatusCode Value=”samlp:Success”/>
    </samlp:Status>
    <saml:Assertion
        xmlns:saml=”urn:oasis:names:tc:SAML:1.0:assertion”
        AssertionID=”#AssertionID#”
        IssueInstant=”#nowDateTime#”
        Issuer=”#issuer#” MajorVersion=”1″
        MinorVersion=”1″>
        <saml:Conditions NotBefore=”#nowDateTime#”
            NotOnOrAfter=”#nowDateTimePlus1#”>
            <saml:AudienceRestrictionCondition>
                <saml:Audience>
                  http://intranet/IdP
                </saml:Audience>
            </saml:AudienceRestrictionCondition>
        </saml:Conditions>
        <saml:AuthenticationStatement 
           AuthenticationInstant=”2007-11-04T13:52:42Z”>
           <saml:Subject>
             <saml:NameIdentifier>#ssouser.id#
             </saml:NameIdentifier>
           </saml:Subject>
        </saml:AuthenticationStatement>
        <saml:AttributeStatement>
         <saml:Attribute AttributeName=”emailaddress”>
         <saml:AttributeValue>#ssouser.email#
         </saml:AttributeValue> 
         </saml:Attribute>
         <saml:Attribute AttributeName=”givenname”>
         <saml:AttributeValue>
             #ssouser.givenname#
           </saml:AttributeValue> 
         </saml:Attribute>
         <saml:Attribute AttributeName=”surname”>  
         <saml:AttributeValue>
         #ssouser.surname#
         </saml:AttributeValue> 
         </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
</samlp:Response>
</cfxml>
</cfoutput>

The above uses an authentication statement for the users identity and attribute statements for additional attributes e.g. users full name, email address etc. The standard doesn’t define which attributes are used. It allows namespace attributes to be used, allowing complete flexibility.

To add a signature element, we use the XMLSecurity library. The ColdFusion variables need coaxing to extract the right java objects. The last line does the actual insertion of a signature element into the SAML.

<cfscript>
samlAssertionElement = 
  samlAssertionXML.getDocumentElement();
samlAssertionDocument =
  samlAssertionElement.GetOwnerDocument();
samlAssertion = samlAssertionDocument
  .getFirstChild();
SignatureSpecNS = CreateObject(“Java”,
  “org.apache.xml.security.utils.Constants”)
  .SigSpecNS;
Init = CreateObject(“Java”, 
   “org.apache.xml.security.Init”).Init().init();
XMLSignatureClass = CreateObject(“Java”, 
   “org.apache.xml.security.signature.XMLSignature”);
sigType =
   XMLSignatureClass.ALGO_ID_SIGNATURE_RSA_SHA1;
signature = XMLSignatureClass
   .init(samlAssertionDocument, 
    javacast(“string”,”"), sigType); 
samlAssertionElement
  .insertBefore(signature 
  .getElement(),samlAssertion.getFirstChild());

</cfscript>

Note that the signature is inside the the XML that we are signing; this is known as an enveloped signature. The following is needed by the XMLSecurity library to define that we will be using an enveloped signature.

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(transformOmitCommentsStr);
transforms.addTransform(transformEnvStr);

To sign the SAML, we need a certificate. A self self certificate can be used. Typically issuer signed certificates are used, and for this the cheapest certificate from Thawte etc is sufficient. Java typically stores these in a key store. You can follow one of the numerous guides to doing this via the command line or use Portecle which is very easy to use. The code to extract a certificate from a key store is as follows

ksfile = CreateObject(“Java”, “java.io.File”)
  .init(storepath);
inputStream = CreateObject(“Java”,
  “java.io.FileInputStream”)
  .init(ksfile);
KeyStoreClass = CreateObject(“Java”
  , “java.security.KeyStore”);        
ks = KeyStoreClass.getInstance(“JKS”); 
ks.load(inputStream,”sso”); 
key = ks.getKey(“sso”,keypw.toCharArray());
cert = ks.getCertificate(certAlias);
publickey = cert.getPublicKey();

We now need to add a digest, signature and key information.

signature.addDocument(“#chr(35)##AssertionID#”
  , transforms);
signature.addKeyInfo(cert);
signature.addKeyInfo(publickey);
signature.sign(key);

In this example we have signed the whole response. You will see examples on the web where first the saml assertion is signed, and then in addition the response is signed as well. This isn’t required when the assertion and response are created at the same time.

Finally, encode this into a HTML page that will do a post to the SP.

<html>
    <body onload=”document.forms[0].submit()”>
        <form action=
http://ServiceProvider/SSO/POST/” method=”post”>
        <input type=”hidden” name=”SAMLRequest”
        value=’#BinaryEncode(
           CharsetDecode(
           samlAssertionXML,”utf-8″)
           ,”Base64″)#’/>
        </form>
    </body>
</html>

Interpreting the XML on the External Service (SP)

On the Service Provider, we do everything in reverse.

First extract the SAML, parse the XML, and verify the signature. There are a few lines of code to set up the various java objects correctly; the last line does the work.

xmlResponse=CharsetEncode(
  BinaryDecode(form.SAMLRequest,”Base64″)
  ,”utf-8″);
docElement= XmlParse(variables.xmlResponse)
  .getDocumentElement();
SignatureConstants=CreateObject(
  “Java”, “org.apache.xml.security.utils.Constants”);
SignatureSpecNS=SignatureConstants.SignatureSpecNS; 
xmlSignatureClass = CreateObject(“Java”,
  “org.apache.xml.security.signature.XMLSignature”);
xmlSignature = xmlSignatureClass 
  .init(docElement.getElementsByTagNameNS
  (SignatureSpecNS,”Signature”)
  .item(0),”");
keyInfo=xmlSignature.getKeyInfo();
X509CertificateResolverCN = 
  “org.apache.xml.security.keys.keyresolver
  .implementations.X509CertificateResolverClass”).
keyResolver=CreateObject(“Java”,
   X509CertificateResolverCN)
  .init();
keyInfo.registerInternalKeyResolver(keyResolver);
x509cert = keyInfo.getX509Certificate();

isValid = xmlSignature.checkSignatureValue(x509cert);

Assuming the SAML has been correctly signed, we now need to establish that the request is from an approved source, is in the right time frame and isn’t a repeat. Finally extract the users identity.

ssoissuer = getSingleValue(XPathIssuer);
conditions = getSingleValue(XPathConditions);
beforeDate = DateConvertISO8601
  (conditions.XmlAttributes.NotBefore, 0);
afterDate = DateConvertISO8601
  (conditions.XmlAttributes.NotOnOrAfter, afterwindow);
certificate = getSingleValue
  (XPathCertificates).XmlText;
reference = getSingleValue(XPathUniqueReference);

// Verify Validity Info
// …

   // Extract User
   ssouser =  getSingleValue(XPathNameIdentifier).xmltext;

Much of the additional code for validity, verifying whether its a new or existing user etc will be very much application dependant.

The extras

The code above uses the function DateConvertISO8601, an implementation of this from David Satz can be found on cflib. The function getSingleValue is a wrapper around XMLSearch.

Finally, the class org.apache.xml.security.Init has an init initializer method that clashes with the ColdFusion init method; in CF 7 you need to call it via java introspection as in the following snippet (thanks to Jon for the correction on this):

InitClass = CreateObject(“Java”,
     “org.apache.xml.security.Init”);
emptyArray = ArrayNew(1);
initMethod=InitClass.GetClass()
  .GetMethod(“init”,emptyArray);
initMethod.invoke(Init,emptyArray);

<cffunction name=”getSingleValue” output=”false”
   access=”public” returntype=”ANY”>
   <!— Expects an array of values to be returned
   from XML data, but only returns the first one —>
   <cfargument name=”xpath” required=”true”>
   <cfargument name=”reqAttrib” required=”No”
     type=”boolean” default=”true”>
    <cfset values=
        XmlSearch(variables.xmlResponse,
        arguments.xpath)>
        <cfif reqAttrib >
         <cfif ArrayLen(values) EQ 0>
           <cfthrow type=”saml”
           message=”Error: No value for: #xpath#”>
        </cfif>
        </cfif>
        <cfif isArray(values)>
            <cfreturn values[1]>
        <cfelse>
            <cfreturn values >
        </cfif>
</cffunction>

More Reading

This PDF has a very clear java implementation. Phil Duba has a set of well written SAML articles on his blog. This uses a wrapper round a java implementation. You may also need to know about the differences between SAML1.1 and SAML 2.0: see saml.xml.org.