Validating a SAML assertion in .NET

SAML is an XML based standard for authorization commonly used in web based, single sign-on applications. The other day at work, we had to build SAML support for one of our applications. We wasted a lot of time debugging the validation of the digital signature on the SAML response. I am documenting some of the issues we faced along the way so that it might be helpful for some other soul.

*[ Warning: I am a noob when it comes to SAML and I will not cover all the steps in validating a SAML assertion. I am documenting only the XMLDSig portion of the validation process. The rest of the steps are pretty straight forward. Just read the SAML documentation to find out more. ] *

A sample SAML assertion would look like this:

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"
      ResponseID="s9d3a145fb36497b87f3d68a699e03021792ed88a"...>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    ...
  </Signature>
  <samlp:Status>
    <samlp:StatusCode Value="samlp:Success"></samlp:StatusCode>
  </samlp:Status>
  <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" ....>
    .....
  </saml:Assertion>
</samlp:Response>

As you can see, we have a signature block which is used for validating the authenticity of the response. A signature block looks like this:

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="#s9d3a145fb36497b87f3d68a699e03021792ed88a">
        <Transforms>
          ....
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>G0hu1eRWOHITHuIefBuoCMHwWQ0=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>
    fcZjbY08uz0w6L....</SignatureValue>
    <KeyInfo>
      <X509Data>
        <X509Certificate>
        MIIDTzCCArigA...</X509Certificate>
      </X509Data>
    </KeyInfo>
</Signature>

Validating the signature

As it turns out, the .net framework has built-in support for verifying XML signatures. It’s all done using a simple class called SignedXml. You can use the below code to verify the digital signature.

private bool IsValidSignature(XmlDocument xmlDoc)
{
    SignedXml signedXml = new SignedXml(xmlDoc);
    XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");

    if (nodeList != null && nodeList.Count > 0)
    {
        signedXml.LoadXml((XmlElement)nodeList[0]);
        return signedXml.CheckSignature();
    }
    return false;
}

The code as such is very simple, but the problem with CheckSignature is that all it returns is a true or a false if it is not able to validate the signature. If it returns a false, you are screwed!

Some Gotchas

Here are some problems we ran into while validating our SAML response.

As for the last part, our response had ResponseID instead of Id. Since the SAML response was generated by a third-party, we did not have any control over this. So we had to overload the GetIdElement method in SignedXml to look for elements with attribute ResponseID instead of Id.

public override XmlElement GetIdElement(XmlDocument document, string idValue)
{
    XmlElement elem = null;
    if ((elem = base.GetIdElement(document, idValue)) == null)
    {
        XmlNodeList nl = document.GetElementsByTagName("*");
        IEnumerator enumerator = nl.GetEnumerator();
        while (enumerator.MoveNext())
        {
            XmlNode node = (XmlNode)enumerator.Current;
            IEnumerator nodeEnum = node.Attributes.GetEnumerator();
            while (nodeEnum.MoveNext())
            {
                XmlAttribute attr = (XmlAttribute)nodeEnum.Current;
                if (attr.LocalName.ToLower() == "responseid"
                          && attr.Value == idValue
                          && node is XmlElement)
                {
                    return (XmlElement)node;
                }
            }
        }
    }
    return elem;
}
comments powered by Disqus