2

We are receiving a standard SAML 2.0 assertion from an Identify Provider, and I am unable to validate it using the only example ColdFusion 9 example code that exists on the internet.

The code below can be found on the internet as an example of how to validate SAML with ColdFusion. See this page: http://blog.tagworldwide.com/?p=19

The SAML XML is being sent to us as a form post. We've got a page set up to detect the incoming assertion. The relevant code is below:

xmlResponse = getHttpRequestData().content.Trim();
docElement = XmlParse(variables.xmlResponse);

Init = CreateObject("Java", "org.apache.xml.security.Init").Init().init();

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.X509CertificateResolver";
keyResolver = CreateObject("Java", X509CertificateResolverCN).init();
keyInfo.registerInternalKeyResolver(keyResolver);
x509cert = keyInfo.getX509Certificate();

isValid = xmlSignature.checkSignatureValue(x509cert);

ColdFusion 9 doesn't have built in libraries to handle x509 validation, so two Java libraries were imported into our ColdFusion installation. These came from the Apache Santuario project. They are:

  • serializer-2.7.1.jar
  • xmlsec-1.5.3.jar

The code runs just fine, but it always outputs "NO", the signature is not valid. I am certain that the assertion should valid properly.

I admit that I am just cargo coding here as I am not familiar enough with Java to truly understand what is going on here.

I've tried everything I can think of. Can anyone offer any tips or ideas on what to check or what to modify to continue troubleshooting?

== UPDATE 1 - Added SAML Assertion Example ==

This sample assertion from Salesforce.com is nearly identical in format to the one we are receiving. Yes our assertion does include a public key in it, just as this one does (though truncated here).

<samlp:Response ID="_257f9d9e9fa14962c0803903a6ccad931245264310738" IssueInstant="2009-06-17T18:45:10.738Z" Version="2.0">
     <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
        https://www.salesforce.com
     </saml:Issuer>

     <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
     </samlp:Status>

     <saml:Assertion ID="_3c39bc0fe7b13769cab2f6f45eba801b1245264310738" 
        IssueInstant="2009-06-17T18:45:10.738Z" Version="2.0">
        <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
           https://www.salesforce.com
        </saml:Issuer>

        <saml:Signature>
           <saml:SignedInfo>
              <saml:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
              <saml:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
              <saml:Reference URI="#_3c39bc0fe7b13769cab2f6f45eba801b1245264310738">
                 <saml:Transforms>
                    <saml:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    <saml:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                       <ec:InclusiveNamespaces PrefixList="ds saml xs"/>
                    </saml:Transform>
                 </saml:Transforms>
                 <saml:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                 <saml:DigestValue>vzR9Hfp8d16576tEDeq/zhpmLoo=
                 </saml:DigestValue>
              </saml:Reference>
           </saml:SignedInfo>
           <saml:SignatureValue>
              AzID5hhJeJlG2llUDvZswNUrlrPtR7S37QYH2W+Un1n8c6kTC
              Xr/lihEKPcA2PZt86eBntFBVDWTRlh/W3yUgGOqQBJMFOVbhK
              M/CbLHbBUVT5TcxIqvsNvIFdjIGNkf1W0SBqRKZOJ6tzxCcLo
              9dXqAyAUkqDpX5+AyltwrdCPNmncUM4dtRPjI05CL1rRaGeyX
              3kkqOL8p0vjm0fazU5tCAJLbYuYgU1LivPSahWNcpvRSlCI4e
              Pn2oiVDyrcc4et12inPMTc2lGIWWWWJyHOPSiXRSkEAIwQVjf
              Qm5cpli44Pv8FCrdGWpEE0yXsPBvDkM9jIzwCYGG2fKaLBag==
           </saml:SignatureValue>
           <saml:KeyInfo>
              <saml:X509Data>
                 <saml:X509Certificate>
                    MIIEATCCAumgAwIBAgIBBTANBgkqhkiG9w0BAQ0FADCBgzELM
                    [Certificate truncated for readability...]
                 </saml:X509Certificate>
              </saml:X509Data>
           </saml:KeyInfo>
        </saml:Signature>

        <saml:Subject>
           <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">
              saml01@salesforce.com
           </saml:NameID>

           <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
           <saml:SubjectConfirmationData NotOnOrAfter="2009-06-17T18:50:10.738Z" 
              Recipient="https://login.www.salesforce.com"/>
           </saml:SubjectConfirmation>
        </saml:Subject>

        <saml:Conditions NotBefore="2009-06-17T18:45:10.738Z" 
           NotOnOrAfter="2009-06-17T18:50:10.738Z">

           <saml:AudienceRestriction>
              <saml:Audience>https://saml.salesforce.com</saml:Audience>
           </saml:AudienceRestriction>
        </saml:Conditions>

        <saml:AuthnStatement AuthnInstant="2009-06-17T18:45:10.738Z">
           <saml:AuthnContext>
              <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
              </saml:AuthnContextClassRef>
           </saml:AuthnContext>
        </saml:AuthnStatement>

        <saml:AttributeStatement>

           <saml:Attribute Name="portal_id">
              <saml:AttributeValue xsi:type="xs:anyType">060D00000000SHZ
              </saml:AttributeValue>
           </saml:Attribute>

           <saml:Attribute Name="organization_id">
              <saml:AttributeValue xsi:type="xs:anyType">00DD0000000F7L5
              </saml:AttributeValue>
           </saml:Attribute>

           <saml:Attribute Name="ssostartpage" 
              NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">

              <saml:AttributeValue xsi:type="xs:anyType">
                 http://www.salesforce.com/security/saml/saml20-gen.jsp
              </saml:AttributeValue>
           </saml:Attribute>

           <saml:Attribute Name="logouturl" 
              NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">

              <saml:AttributeValue xsi:type="xs:string">
                 http://www.salesforce.com/security/del_auth/SsoLogoutPage.html
              </saml:AttributeValue>
           </saml:Attribute>
        </saml:AttributeStatement>
     </saml:Assertion>
  </samlp:Response>

== UPDATE 2 - Used keytool to add public key to java keystore ==

Added public key to java keystore file "cacerts" using keytool command. I can see that the public key has now been added to our cacerts file and that the cert is marked as "trusted".

We were sent their public key in a *.pem file, so I used that to add their key to the keystore. I also tried converting the *.pem file to a *.der file and importing that. Both worked just fine. However, my code still returns "NO" for isValid. Ugh.

The public key in the *.pem file exactly matches the public key that comes in the SAML assertion.

  • 2
    Have you performed any configuration other than the code snippet above? I don't know ColdFusion, but you will need to somehow register or reference a known public key from the IdP so that your Java code can resolve and verify that the digital signature is valid or actually created from the IdP's private key. In other server runtimes (like JavaEE application servers) this is usually a configuration step to "import" the public key. In standalone Java programs, I usually see this done with a file reference to a java keystore file (lotsss on the Internet about those). – Scott Heaberlin Oct 09 '14 at 01:40
  • I suspect that you are right about this. However, whenever I look at any of the meager ColdFusion examples about validating assertions, they never mention anything about keystores. Maybe it's just assumed that the reader has already set this up. Looks like I have some more research to do, as I have no idea how to use a keystore. – SentryGoingUp Oct 09 '14 at 20:21
  • Looking at that code, I would bet that it expects the public key to be in the assertion. – Andrew K. Oct 09 '14 at 20:51
  • Agreed - can you post a sample assertion you're getting? Also - at the bottom of the blog post you listed, there is an example of loading a Java key store from the filesystem. A good tutorial for loading/creating a java keystore file via the Java `keytool` command is [here](https://www.sslshopper.com/article-most-common-java-keytool-keystore-commands.html). – Scott Heaberlin Oct 10 '14 at 01:06
  • @scotth - Just curious. Do they *have* to get the key from their keystore, or could it simply be [extracted from the file they received](http://stackoverflow.com/questions/26007505/consuming-a-saml-2-0-assertion-with-coldfusion-what-do-i-do-with-a-public-key)? – Leigh Oct 10 '14 at 02:57
  • @AndyK.-PingIdentity - Yes, there is a public key embedded in the SAML assertion that is sent to us. They have also sent us a separate *.pem file that also contains their public key. – SentryGoingUp Oct 10 '14 at 15:47
  • @scotth - I successfully added the public key to our java keystore file "cacerts" using keytool. I imported the *.pem file into cacerts successfully. I also tried converting the *.pem file to a *.der file, and that imported fine as well. My code still returns NO to isValid. – SentryGoingUp Oct 10 '14 at 15:57
  • @Leigh - The question in that link is also mine... it was posted before this, more specific question was asked. I will update both with solutions once I get this figured out. – SentryGoingUp Oct 10 '14 at 15:59
  • @SentryGoingUp - Yep. Sorry I was trying not to disrupt your question :) but wanted to indicate you did have the public key in a .pem file (just in case it was not included in the assertion) Mainly I was curious too about the process .. and whether or not you actually *had to* import it into your keystore or could just extract it from the file they sent you. – Leigh Oct 10 '14 at 16:06
  • @Leigh - Yeah, much of my confusion comes from not knowing exactly what to do with the public key in the *.pem file versus the one in the assertion. They are identical. I'm thinking that I should just store the one in the *.pem file in our DB and then just character match it against any that come through assertions. If it matches, it's good to go.... I guess. :) – SentryGoingUp Oct 10 '14 at 16:14
  • If there is a key in the assertion it can be used (such as `KeyInfo` in the updated post's SAML). I just didn't see code or XML indicating such - and the linked blog post reads a jks from file. Since there is a key in KeyInfo, the java keystore is not required (though I personally believe that approach is more secure, since you verify with more than just the SAML doc itself... but that is debatable and depends on cert authority). – Scott Heaberlin Oct 10 '14 at 23:26

2 Answers2

3

If it is not validating or you get "Cannot resolve element with ID." Add the third line below. What is happening is the newer version of Apache Santuario no longer assumes the IdAttribute is "ID". You need to manually set it.

xmlResponse = getHttpRequestData().content.Trim();
docElement = XmlParse(variables.xmlResponse);
docElement.setIdAttribute("ID",true); //Add this line
eSmeby
  • 46
  • 2
  • I'm going to accept this as the answer because the only solution I was able to find was to use slightly older versions of the Santuario libraries. Using the latest libraries did not work... hence the solution above is probably the right answer. – SentryGoingUp Feb 12 '15 at 19:32
0

Since you mention Apache Santuario, here's a Java code sample from that project that involves validating an XML digital signature.

That sample uses the public key from the X509Certificate instead of the cert itself. Your code sample seems to be using the cert itself:

keyInfo = xmlSignature.getKeyInfo();
X509CertificateResolverCN = "org.apache.xml.security.keys.keyresolver.implementations.X509CertificateResolver";
keyResolver = CreateObject("Java", X509CertificateResolverCN).init();
keyInfo.registerInternalKeyResolver(keyResolver);
x509cert = keyInfo.getX509Certificate();

isValid = xmlSignature.checkSignatureValue(x509cert);

How about adjusting this to use the java.security.PublicKey from the X509Certificate:

keyInfo = xmlSignature.getKeyInfo();
X509CertificateResolverCN = "org.apache.xml.security.keys.keyresolver.implementations.X509CertificateResolver";
keyResolver = CreateObject("Java", X509CertificateResolverCN).init();
keyInfo.registerInternalKeyResolver(keyResolver);
x509cert = keyInfo.getX509Certificate();
publicKey = x509cert.getPublicKey()

isValid = xmlSignature.checkSignatureValue(publicKey);

Disclaimer: I've never written a line of CF in my life, so if it works but there are adjustments to make please comment and I'll adjust it.

Scott Heaberlin
  • 3,364
  • 1
  • 23
  • 22